Files
sanguo_vnpy_v2/sanguo_trader/account.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

76 lines
3.0 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.
"""模拟盘总账:资金 T+0 / 合并持仓 / 净值盯市(spec §7 双层记账总账层)。
- cash 资金 T+0:卖出回笼资金当日即可再买(A 股硬规则)
- 股票 T+1:买入持仓由 PositionLedger.frozen 管,次日 unfreeze_all 解冻
- transfer_feematcher 已出双向总额(review H3),Account 直接用,不再 ×2
- 资金检查 cash_enough 在买单 apply 前由 PaperEngine 调用
"""
from .models import OrderSide, PaperTrade
from .position_ledger import PositionLedger
class Account:
def __init__(self, initial_capital: float) -> None:
self.cash: float = float(initial_capital)
self.positions: dict[str, PositionLedger] = {}
self.market_value: float = 0.0
def _position(self, symbol: str) -> PositionLedger:
if symbol not in self.positions:
self.positions[symbol] = PositionLedger(symbol)
return self.positions[symbol]
def cash_enough(self, trade: PaperTrade) -> bool:
"""买单检查现金是否足够(卖出不检查,持仓检查在 PositionLedger)。"""
if trade.side != OrderSide.BUY:
return True
cost = trade.price * trade.volume + trade.commission + trade.transfer_fee
return self.cash >= cost
def apply_trade(self, trade: PaperTrade) -> None:
pos = self._position(trade.symbol)
if trade.side == OrderSide.BUY:
cost = trade.price * trade.volume + trade.commission + trade.transfer_fee
self.cash -= cost
pos.apply_buy(trade.price, trade.volume)
else:
proceeds = (
trade.price * trade.volume
- trade.commission - trade.stamp_duty - trade.transfer_fee
)
self.cash += proceeds # 资金 T+0:卖出现金立即可用
pos.apply_sell(trade.price, trade.volume)
def unfreeze_all(self) -> None:
"""每日开盘前:T+1 解冻所有持仓。"""
for p in self.positions.values():
p.unfreeze()
def apply_cash_dividend(self, symbol: str, per_share: float) -> None:
"""现金分红到账:cash += per_share × 持仓量(spec §295)。
按 A 股惯例按除权日前一交易日持仓量派发;无持仓/非正值 no-op。
"""
if per_share <= 0:
return
pos = self.positions.get(symbol)
if pos is None or pos.volume <= 0:
return
self.cash += per_share * pos.volume
def mark_to_market(self, bars_raw: dict[str, float],
prev_close: dict[str, float] | None = None) -> None:
"""按 raw 收盘价重估合并持仓市值(spec §295 停牌盯市兜底)。
bar 缺失(停牌)→ 用前日 close 兜底,再退到 avg_price。
"""
prev_close = prev_close or {}
self.market_value = sum(
p.volume * bars_raw.get(sym, prev_close.get(sym, p.avg_price))
for sym, p in self.positions.items()
)
@property
def equity(self) -> float:
return self.cash + self.market_value