Files
sanguo_vnpy_v2/sanguo_trader/matcher.py
T

93 lines
3.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""A 股撮合纯函数(match_session / 费率 / 100 股 / 封板)。
match_bar 必须是 raw 价格(spec §3.3 / §6.2)。所有费率来自 AccountConfig。
拒单返回 PaperReject;成交返回 PaperTrade。
资金检查由 Account 在 apply 前负责(matcher 不看资金)。
"""
import pandas as pd
from .limit import is_locked_for_buy_symbol, is_locked_for_sell_symbol
from .models import (
AccountConfig,
MatchSession,
OrderSide,
PaperOrder,
PaperReject,
PaperTrade,
)
MIN_LOT = 100
def cross_order(
order: PaperOrder,
match_bar: pd.Series,
prev_close_raw: float,
cfg: AccountConfig,
is_st: bool = False,
) -> PaperTrade | PaperReject:
"""单笔订单撮合。
步骤:
1. 涨跌停封板拒单(raw,按板块幅度)
2. 成交价(NEXT_OPEN=bar.open / CURRENT_CLOSE=bar.close
3. 限价单触价检查
4. 100 股取整(买入向下取整;卖出允许零股)
5. 费用(佣金 min 5 元 / 印花税仅卖 / 过户费单边)
"""
symbol = order.symbol
bar_date = str(match_bar.get("date", ""))
# 1. 涨跌停封板拒单(raw
if order.side == OrderSide.BUY and is_locked_for_buy_symbol(
match_bar, symbol, prev_close_raw, cfg, is_st
):
return PaperReject(order.strategy_id, symbol, "limit_up_locked", bar_date)
if order.side == OrderSide.SELL and is_locked_for_sell_symbol(
match_bar, symbol, prev_close_raw, cfg, is_st
):
return PaperReject(order.strategy_id, symbol, "limit_down_locked", bar_date)
# 2. 成交价(按 match_session
if order.match_session == MatchSession.NEXT_OPEN:
fill_price = match_bar["open"]
elif order.match_session == MatchSession.CURRENT_CLOSE:
fill_price = match_bar["close"]
else:
return PaperReject(
order.strategy_id, symbol, "unsupported_match_session", bar_date
)
# 3. 限价单触价
if not order.is_market:
if order.side == OrderSide.BUY and fill_price > order.price:
return PaperReject(order.strategy_id, symbol, "limit_not_touched", bar_date)
if order.side == OrderSide.SELL and fill_price < order.price:
return PaperReject(order.strategy_id, symbol, "limit_not_touched", bar_date)
# 4. 100 股取整(买入向下取整;卖出不取整,允许零股退出)
volume = order.volume
if order.side == OrderSide.BUY:
volume = (volume // MIN_LOT) * MIN_LOT
if volume < MIN_LOT:
return PaperReject(order.strategy_id, symbol, "volume_below_min_lot", bar_date)
# 5. 费用
gross = volume * fill_price
commission = max(gross * cfg.rate, cfg.min_commission)
stamp_duty = gross * cfg.stamp_duty_rate if order.side == OrderSide.SELL else 0.0
transfer_fee = gross * cfg.transfer_fee_rate # 单边;Account 算双向 ×2
return PaperTrade(
strategy_id=order.strategy_id,
symbol=symbol,
side=order.side,
price=fill_price,
volume=volume,
commission=commission,
stamp_duty=stamp_duty,
transfer_fee=transfer_fee,
bar_date=bar_date,
match_session=order.match_session,
)