Files
sanguo_vnpy_v2/sanguo_trader/strategy_runner.py
T
claude_dev 164690373f feat(trader): C期分期项收尾—资金占用成本+分红送股+_restore_ledger修复
- 资金占用成本(spec§195): StrategyRunner.daily_borrow_cost(used×risk_free/365)
  归因per_strategy_pnl(不碰account总账, account.equity真实净值不变);
  config risk_free_rate=0.02; engine.step mark_to_market后计扣; =0向后兼容跳过
- 分红送股(spec§295): dividend_source.py(akshare stock_history_dividend_detail,
  实测600000/000001纯现金分红); PositionLedger.apply_split(volume×factor/avg÷factor);
  Account.apply_cash_dividend; engine._apply_dividends(除权日调整,现金先split后);
  mark_to_market停牌prev_close兜底(今收→前收→均价); _run_replay注入dividends日历
- 修_restore_ledger预存bug: PositionLedger.__init__加volume/frozen/avg_price参数
  (原只symbol, live_orchestrator跨日恢复4参数调用会TypeError, 首次step空仓未暴露)
- 139 passed(119基准+20分红+3占用成本), 无回归
- live_step dividends注入待分期项(每日拉全市场分红慢, 需run_daily_update预拉日历)
2026-07-10 08:44:35 +08:00

74 lines
3.1 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 daily_borrow_cost(self, closes: dict[str, float] | None = None,
risk_free_rate: float = 0.0) -> float:
"""资金占用日成本 = 占用资金 × 年化无风险利率 / 365(spec §195 归因用)。
纯归因记账:返回值由 PaperEngine 计入 per_strategy_pnl,不扣 account.cash。
"""
return self.used_allocation(closes) * risk_free_rate / 365.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