"""Account + StrategyRunner 双层记账测试(spec §7)。""" import pytest from sanguo_trader.account import Account from sanguo_trader.strategy_runner import StrategyRunner from sanguo_trader.models import MatchSession, OrderSide, PaperTrade def mktrade(side, price=10.0, volume=100, commission=5.0, stamp=0.0, transfer_fee=0.02, strategy_id="s1", symbol="600000"): return PaperTrade( strategy_id, symbol, side, price, volume, commission, stamp, transfer_fee, "", MatchSession.NEXT_OPEN, ) def test_capital_t0_sell_then_buy_immediately(): """资金 T+0:卖出现金立即可用于再买。""" acc = Account(initial_capital=1_000_000) acc.apply_trade(mktrade(OrderSide.BUY, price=10.0, volume=100)) acc.unfreeze_all() cash_after_buy = acc.cash acc.apply_trade(mktrade(OrderSide.SELL, price=11.0, volume=100, stamp=0.55)) assert acc.cash > cash_after_buy # 卖出后现金立即增加 assert acc.cash_enough(mktrade(OrderSide.BUY, price=11.0, volume=50)) # 可立即再买 def test_share_t1_buy_frozen_until_unfreeze(): """股票 T+1:买入当日 frozen,unfreeze 后才可卖。""" acc = Account(initial_capital=1_000_000) acc.apply_trade(mktrade(OrderSide.BUY, volume=100)) pos = acc.positions["600000"] assert pos.frozen == 100 assert pos.available == 0 acc.unfreeze_all() assert pos.available == 100 def test_cash_enough_insufficient_rejected(): acc = Account(initial_capital=1000) # cost = 10*100 + 5 + 0.02 = 1005.02 > 1000 assert acc.cash_enough(mktrade(OrderSide.BUY, price=10.0, volume=100)) is False def test_transfer_fee_not_doubled_on_buy(): """review H3:matcher 出双向 transfer_fee,Account 直接用不再 ×2。""" acc = Account(initial_capital=1_000_000) acc.apply_trade(mktrade(OrderSide.BUY, price=10.0, volume=100, transfer_fee=0.02)) assert acc.cash == pytest.approx(1_000_000 - 1005.02) def test_double_entry_account_equals_sum_of_runners(): """双层记账一致性:总账持仓 = 各分户持仓之和。""" acc = Account(initial_capital=1_000_000) r1 = StrategyRunner("s1") r2 = StrategyRunner("s2") t1 = mktrade(OrderSide.BUY, volume=100, strategy_id="s1") t2 = mktrade(OrderSide.BUY, volume=200, strategy_id="s2") for t in (t1, t2): acc.apply_trade(t) r1.apply_trade(t1) r2.apply_trade(t2) assert acc.positions["600000"].volume == 300 assert r1.positions["600000"].volume + r2.positions["600000"].volume == 300 def test_runner_realized_pnl_on_sell(): acc = Account(initial_capital=1_000_000) runner = StrategyRunner("s1") buy_t = mktrade(OrderSide.BUY, price=10.0, volume=100, strategy_id="s1") acc.apply_trade(buy_t) runner.apply_trade(buy_t) acc.unfreeze_all() runner.unfreeze_all() sell_t = mktrade(OrderSide.SELL, price=12.0, volume=100, stamp=0.6, strategy_id="s1") acc.apply_trade(sell_t) runner.apply_trade(sell_t) # (12-10)*100 - 5(佣) - 0.6(印) - 0.02(过) = 194.38 assert runner.realized_pnl == pytest.approx(194.38) def test_equity_mark_to_market(): acc = Account(initial_capital=1_000_000) acc.apply_trade(mktrade(OrderSide.BUY, price=10.0, volume=100)) acc.unfreeze_all() acc.mark_to_market({"600000": 11.0}) assert acc.market_value == pytest.approx(1100.0) assert acc.equity == pytest.approx(acc.cash + 1100.0)