93 lines
3.2 KiB
Python
93 lines
3.2 KiB
Python
"""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,
|
||
)
|