164690373f
- 资金占用成本(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预拉日历)
58 lines
2.6 KiB
Python
58 lines
2.6 KiB
Python
"""单标的持仓对象(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
|