"""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