feat(trader): Account总账+StrategyRunner分户(双层记账/资金T0/股票T1)
- Account: cash资金T0/合并持仓/equity盯市/cash_enough买单检查 - StrategyRunner: 分户持仓+realized_pnl归因/unrealized_pnl - transfer_fee 直接用(matcher已双向,不再×2,review H3) - unfreeze_all 对称(总账+分户,T+1每日解冻) 7 tests passed.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user