05d74fc2c1
M1: PaperOrder+limit+matcher 加 listing_days(创业/科创/北交所前5日不锁,0=已过) M3: is_locked_for_*_symbol cfg 注解 AccountConfig M4: matcher NaN bar 拒单 bar_missing L3: PositionLedger price/volume 正数校验 L5: PaperOrder __post_init__ volume 类型校验(拒 float/bool) M5: current_close 契约 docstring + H3 残留注释修正(transfer_fee 双向) 79 tests passed.
45 lines
2.0 KiB
Python
45 lines
2.0 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):
|
|
self.symbol: str = symbol
|
|
self.volume: int = 0
|
|
self.frozen: int = 0 # T+1 当日买入冻结
|
|
self.avg_price: float = 0.0
|
|
|
|
@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 unfreeze(self) -> None:
|
|
"""次日开盘前调用:frozen → available。"""
|
|
self.frozen = 0
|