"""模拟盘总账:资金 T+0 / 合并持仓 / 净值盯市(spec §7 双层记账总账层)。 - cash 资金 T+0:卖出回笼资金当日即可再买(A 股硬规则) - 股票 T+1:买入持仓由 PositionLedger.frozen 管,次日 unfreeze_all 解冻 - transfer_fee:matcher 已出双向总额(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