"""单标的持仓对象(raw 计均价、T+1 冻结)。 mutable,被 Account / StrategyRunner 持有(spec §6.3 双层记账)。 - 均价:移动加权平均,买入刷新;卖出不影响剩余持仓成本(A 股惯例) - T+1:买入当日 frozen,次日开盘前由 Account.unfreeze_all() 解冻 - 零股:卖出允许零股(退出持仓基本操作),买入由 matcher 保证 100 股整取 """ class PositionLedger: def __init__(self, symbol: str, volume: int = 0, frozen: int = 0, avg_price: float = 0.0): self.symbol: str = symbol self.volume: int = volume self.frozen: int = frozen # T+1 当日买入冻结 self.avg_price: float = avg_price @property def available(self) -> int: """可卖出量 = 总持仓 - T+1 冻结。""" return self.volume - self.frozen def apply_buy(self, price: float, volume: int) -> None: """买入:刷新移动加权均价,新买入量计入 frozen(T+1)。""" if volume <= 0 or price <= 0: raise ValueError(f"price/volume 必须为正: price={price}, volume={volume}") total_cost = self.avg_price * self.volume + price * volume self.volume += volume self.avg_price = total_cost / self.volume if self.volume else 0.0 self.frozen += volume # T+1 def apply_sell(self, price: float, volume: int) -> None: """卖出:扣减持仓量。price 保留接口对称(不影响剩余持仓均价)。 清仓时 avg_price 归零(避免下一次买入残留历史成本)。""" if volume <= 0 or price <= 0: raise ValueError(f"price/volume 必须为正: price={price}, volume={volume}") if volume > self.available: raise ValueError( f"卖出超过可卖量: want {volume}, available {self.available}" ) self.volume -= volume if self.volume == 0: self.avg_price = 0.0 def apply_split(self, factor: float) -> None: """送股/转增:volume ×= factor,avg_price /= factor(总市值不变,spec §295)。 factor=(10+送转)/10,对 100 股整数倍持仓结果恒为整数。 factor<=0 非法;空持仓 no-op。 """ if factor <= 0: raise ValueError(f"split factor 必须为正: {factor}") if self.volume <= 0: return self.volume = int(round(self.volume * factor)) self.avg_price = self.avg_price / factor def unfreeze(self) -> None: """次日开盘前调用:frozen → available。""" self.frozen = 0