fix(trader): matcher slippage/过户费双向/限价超涨停拒单 (H2/H3/H4)

H2: fill_price 应用 slippage(买+/卖-,默认0不影响)
H3: 过户费改双向(matcher 直接×2,Account 不再×2,删单边注释)
H4: 限价单超涨停价拒单(price_above_limit/price_below_limit)
69 tests passed.
This commit is contained in:
2026-07-07 11:15:19 +08:00
parent f940841a9a
commit b874be1d84
2 changed files with 112 additions and 11 deletions
+76 -3
View File
@@ -204,11 +204,11 @@ def test_stamp_duty_only_on_sell():
assert t_sell.stamp_duty == pytest.approx(0.5)
def test_transfer_fee_single_sided_in_trade():
def test_transfer_fee_double_sided_in_trade():
t = cross_order(buy(market=True), mkbar(10, 10, 10, 10), PREV, CFG)
assert isinstance(t, PaperTrade)
# 单边 100*10*0.00001 = 0.01trade 里存单边,Account 算 ×2
assert t.transfer_fee == pytest.approx(0.01)
# 双向 100*10*0.00001*2 = 0.02matcher 直接出双向总额
assert t.transfer_fee == pytest.approx(0.02)
def test_trade_carries_match_session():
@@ -232,3 +232,76 @@ def test_trade_carries_strategy_id_and_symbol():
assert isinstance(t, PaperTrade)
assert t.strategy_id == "my_strat"
assert t.symbol == "300750"
# ---- H4: 限价超涨停 / 跌停未拒 ----
def test_limit_buy_above_limit_up_rejected():
"""限价买单价格超涨停 → reject price_above_limit(触价检查前先查涨跌停)。"""
# 主板 600000 prev 10.0 → 涨停 11.0;限价 11.50 超涨停
o = PaperOrder("s1", "600000", OrderSide.BUY, 11.50, 100, is_market=False)
r = cross_order(o, mkbar(10.5, 11, 10.2, 10.8), PREV, CFG)
assert isinstance(r, PaperReject) and r.reason == "price_above_limit"
def test_limit_sell_below_limit_down_rejected():
"""限价卖单价格低于跌停 → reject price_below_limit。"""
# 主板 600000 prev 10.0 → 跌停 9.0;限价 8.50 跌破
o = PaperOrder("s1", "600000", OrderSide.SELL, 8.50, 100, is_market=False)
r = cross_order(o, mkbar(10.5, 11, 10.2, 10.8), PREV, CFG)
assert isinstance(r, PaperReject) and r.reason == "price_below_limit"
def test_limit_buy_at_limit_up_not_rejected():
"""限价 == 涨停价 不拒(严格 >,等号允许)。"""
o = PaperOrder("s1", "600000", OrderSide.BUY, 11.0, 100, is_market=False)
t = cross_order(o, mkbar(10.8, 11.0, 10.5, 10.9), PREV, CFG)
assert isinstance(t, PaperTrade) and t.price == 10.8
def test_limit_buy_above_gem_limit_up_rejected():
"""创业板 20% 涨停边界:300750 prev 10.0 → 涨停 12.0;限价 12.50 拒单。"""
o = PaperOrder("s1", "300750", OrderSide.BUY, 12.50, 100, is_market=False)
r = cross_order(o, mkbar(10.5, 11, 10.2, 10.8), 10.0, CFG)
assert isinstance(r, PaperReject) and r.reason == "price_above_limit"
# ---- H2: slippage 应用到 fill_price ----
def test_slippage_added_to_buy_fill():
"""买单 fill_price += slippage。"""
cfg = AccountConfig(initial_capital=1_000_000, slippage=0.05)
t = cross_order(buy(market=True), mkbar(10.0, 10, 10, 10), PREV, cfg)
assert isinstance(t, PaperTrade)
assert t.price == pytest.approx(10.05)
def test_slippage_subtracted_from_sell_fill():
"""卖单 fill_price -= slippage。"""
cfg = AccountConfig(initial_capital=1_000_000, slippage=0.05)
t = cross_order(sell(market=True), mkbar(10.0, 10, 10, 10), PREV, cfg)
assert isinstance(t, PaperTrade)
assert t.price == pytest.approx(9.95)
def test_zero_slippage_preserves_fill_price():
"""默认 slippage=0 → fill_price 不变(向后兼容)。"""
t = cross_order(buy(market=True), mkbar(10.0, 10, 10, 10), PREV, CFG)
assert isinstance(t, PaperTrade)
assert t.price == 10.0
def test_slippage_affects_gross_for_fees():
"""slippage 后的 fill_price 用于费用计算(更真实)。"""
cfg = AccountConfig(initial_capital=1_000_000, slippage=0.05)
t = cross_order(sell(market=True), mkbar(10.0, 10, 10, 10), PREV, cfg)
assert isinstance(t, PaperTrade)
# 卖单:fill=9.95gross=100*9.95=995.0
# stamp_duty = 995.0 * 0.0005 = 0.4975
assert t.stamp_duty == pytest.approx(995.0 * 0.0005)
# ---- H3: 过户费双向(已有 test_transfer_fee_double_sided_in_trade 重命名断言更新)----
def test_transfer_fee_double_sided_on_sell_too():
"""卖单过户费同样双向(沪深一致)。"""
t = cross_order(sell(market=True), mkbar(10, 10, 10, 10), PREV, CFG)
assert isinstance(t, PaperTrade)
assert t.transfer_fee == pytest.approx(0.02)