fix(trader): M+L 接口校验 (listing_days/NaN/输入校验/类型注数) review
M1: PaperOrder+limit+matcher 加 listing_days(创业/科创/北交所前5日不锁,0=已过) M3: is_locked_for_*_symbol cfg 注解 AccountConfig M4: matcher NaN bar 拒单 bar_missing L3: PositionLedger price/volume 正数校验 L5: PaperOrder __post_init__ volume 类型校验(拒 float/bool) M5: current_close 契约 docstring + H3 残留注释修正(transfer_fee 双向) 79 tests passed.
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
"""C-S0 review M+L 修复测试:listing_days / NaN bar / 输入校验 / volume 类型。"""
|
||||
import pandas as pd
|
||||
import pytest
|
||||
from sanguo_trader.models import AccountConfig, PaperOrder, OrderSide
|
||||
from sanguo_trader.limit import is_locked_for_buy_symbol
|
||||
from sanguo_trader.matcher import cross_order
|
||||
from sanguo_trader.position_ledger import PositionLedger
|
||||
|
||||
CFG = AccountConfig(initial_capital=1_000_000)
|
||||
|
||||
|
||||
def bar(o, h, l, c):
|
||||
return pd.Series({"open": o, "high": h, "low": l, "close": c})
|
||||
|
||||
|
||||
# ---- M1: listing_days 新股前 5 日无涨跌幅(创业/科创/北交所)----
|
||||
def test_new_stock_gem_first_5_days_no_limit():
|
||||
# 创业板 300750 第 1 日,一字涨停板也不锁
|
||||
b = bar(12.0, 12.0, 12.0, 12.0)
|
||||
assert is_locked_for_buy_symbol(b, "300750", 10.0, CFG, is_st=False, listing_days=1) is False
|
||||
|
||||
|
||||
def test_new_stock_after_5_days_locked():
|
||||
# 创业板第 6 日恢复 ±20%,涨停一字板锁
|
||||
b = bar(12.0, 12.0, 12.0, 12.0)
|
||||
assert is_locked_for_buy_symbol(b, "300750", 10.0, CFG, is_st=False, listing_days=6) is True
|
||||
|
||||
|
||||
def test_main_board_new_stock_still_locked_by_10pct():
|
||||
# 主板新股不享受前 5 日豁免(±44% 首版未实现,按普通 ±10% 锁)
|
||||
up = 11.0
|
||||
b = bar(up, up, up, up)
|
||||
assert is_locked_for_buy_symbol(b, "600000", 10.0, CFG, is_st=False, listing_days=1) is True
|
||||
|
||||
|
||||
def test_matcher_passes_listing_days_to_unlock():
|
||||
# 端到端:创业板新股 listing_days=1,一字板 matcher 不拒单(成交)
|
||||
b = bar(12.0, 12.0, 12.0, 12.0)
|
||||
o = PaperOrder("s", "300750", OrderSide.BUY, 0, 100, is_market=True, listing_days=1)
|
||||
t = cross_order(o, b, 10.0, CFG)
|
||||
assert not hasattr(t, "reason") # 成交(非拒单)
|
||||
assert t.price == 12.0
|
||||
|
||||
|
||||
# ---- M4: NaN bar 拒单 ----
|
||||
def test_nan_bar_rejected_bar_missing():
|
||||
nan = float("nan")
|
||||
b = bar(nan, nan, nan, nan)
|
||||
r = cross_order(PaperOrder("s", "600000", OrderSide.BUY, 0, 100, True), b, 10.0, CFG)
|
||||
assert hasattr(r, "reason") and r.reason == "bar_missing"
|
||||
|
||||
|
||||
# ---- L3: PositionLedger 输入校验 ----
|
||||
def test_position_ledger_rejects_nonpositive_buy():
|
||||
p = PositionLedger("600000")
|
||||
with pytest.raises(ValueError):
|
||||
p.apply_buy(price=-1, volume=100)
|
||||
with pytest.raises(ValueError):
|
||||
p.apply_buy(price=10, volume=0)
|
||||
|
||||
|
||||
def test_position_ledger_rejects_nonpositive_sell():
|
||||
p = PositionLedger("600000")
|
||||
p.apply_buy(10.0, 100)
|
||||
p.unfreeze()
|
||||
with pytest.raises(ValueError):
|
||||
p.apply_sell(price=0, volume=100)
|
||||
|
||||
|
||||
# ---- L5: PaperOrder volume 类型校验 ----
|
||||
def test_paper_order_rejects_float_volume():
|
||||
with pytest.raises(TypeError):
|
||||
PaperOrder("s", "600000", OrderSide.BUY, 10.0, 100.5, True)
|
||||
|
||||
|
||||
def test_paper_order_rejects_bool_volume():
|
||||
with pytest.raises(TypeError):
|
||||
PaperOrder("s", "600000", OrderSide.BUY, 10.0, True, True)
|
||||
|
||||
|
||||
def test_paper_order_accepts_int_volume_and_default_listing_days():
|
||||
o = PaperOrder("s", "600000", OrderSide.BUY, 10.0, 100, True)
|
||||
assert o.volume == 100
|
||||
assert o.listing_days == 0
|
||||
Reference in New Issue
Block a user