Files
sanguo_vnpy_v2/sanguo_trader/strategy_runner.py
T
claude_dev 193064c953 feat(trader): 软限额max_allocation(分期项)—每策略资金额度消除顺序依赖
spec §195: 多策略并发下单"先到后到"不可复现 → 每策略独立max_allocation
- StrategyRunner: max_allocation字段(默认inf) + used_allocation(持仓市值)
- engine._match: BUY cash_enough后查 used+成交额>max_allocation → 拒单max_allocation_exceeded
- live_orchestrator: runner传max_allocation(默认initial_capital)
- routes_paper: StrategyCfg加max_allocation(API→DB→live_step数据流)
- test_soft_limit: 3测试(累计超限拒单/默认不限/SELL不受限)

116 passed(113旧+3新), 无回归.
2026-07-09 22:05:35 +08:00

66 lines
2.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""模拟盘分户账:每策略持仓 + 已实现盈亏归因(spec §7 双层记账分户层)。
与 Account 共享同一笔 tradeAccount 记合并总账,StrategyRunner 记该策略归因。
"""
from .models import OrderSide, PaperTrade
from .position_ledger import PositionLedger
class StrategyRunner:
def __init__(self, strategy_id: str, strategy=None, paper_cta_engine=None,
symbol: str = "", max_allocation: float = float("inf")) -> None:
self.strategy_id: str = strategy_id
self.strategy = strategy
self.paper_cta_engine = paper_cta_engine
self.symbol: str = symbol
self.max_allocation: float = max_allocation # 软限额(spec §195),默认不限
self.positions: dict[str, PositionLedger] = {}
self.realized_pnl: float = 0.0
self.commission_paid: float = 0.0
def used_allocation(self, closes: dict[str, float] | None = None) -> float:
"""该策略已占用资金 = 持仓市值(spec §195 软限额检查用)。
closes 为当日 raw 收盘价 {symbol: price};缺省股票用 avg_price 兜底。
"""
closes = closes or {}
return sum(
p.volume * closes.get(sym, p.avg_price)
for sym, p in self.positions.items() if p.volume > 0
)
def _position(self, symbol: str) -> PositionLedger:
if symbol not in self.positions:
self.positions[symbol] = PositionLedger(symbol)
return self.positions[symbol]
def apply_trade(self, trade: PaperTrade) -> None:
pos = self._position(trade.symbol)
if trade.side == OrderSide.BUY:
pos.apply_buy(trade.price, trade.volume)
self.commission_paid += trade.commission + trade.transfer_fee
else:
avg = pos.avg_price
realized = (
(trade.price - avg) * trade.volume
- trade.commission - trade.stamp_duty - trade.transfer_fee
)
self.realized_pnl += realized
self.commission_paid += (
trade.commission + trade.stamp_duty + trade.transfer_fee
)
pos.apply_sell(trade.price, trade.volume)
def unfreeze_all(self) -> None:
"""每日开盘前:T+1 解冻分户持仓(与 Account.unfreeze_all 对称)。"""
for p in self.positions.values():
p.unfreeze()
def unrealized_pnl(self, bars_raw: dict[str, float]) -> float:
"""按 raw 收盘价算浮动盈亏(未实现)。"""
total = 0.0
for sym, p in self.positions.items():
if p.volume > 0:
total += (bars_raw.get(sym, p.avg_price) - p.avg_price) * p.volume
return total