背景
承接 #111(保护性出场遇 session/DB 分叉钳到实仓全平)。该 PR 已确保 DB 实仓被钳量全平、不裸空、止损不再被静默吃掉,但留下一个 session/DB 对账层面的缺口,在 PR 注释里已诚实标注、刻意未塞进该 PR(范围控制)。本 issue 跟进。
问题
钳量场景(前置分叉源自同账户 HTTP /orders/submit 卖单)下:
- 保护性出场 SELL 1.0 被钳到 DB 实仓 0.5 成交 → DB 全平(0)。
LiveEngineSession.confirm_fill 按钳后量 0.5 增量减仓 → session portfolio 1.0 → 0.5(残留分叉:session 0.5 vs DB 0)。
PositionGuard 首次触发时已把该 inst 记入 _pending_exit_insts;confirm_fill 不清它(只有 reject_order 的保护性单分支、或 pos 转 flat 才清,见 position_guard.evaluate)。
- session 残仓非 flat → 后续 bar
guard.evaluate 命中 pending 去重 → 对该 inst 静默 skip、不再发出场单。
影响
- 此刻 DB 实仓已 0、无即时暴露(本身不构成风险)。
- 隐患:若策略此后在该 inst 重新建仓,
guard 因 _pending_exit_insts 未清仍跳过 → 新建持仓得不到 PositionGuard 灾难止损保护,直到 run restart / reconcile。
- 触发条件较罕见(需同账户同标的被 HTTP 写路径减仓,而 live runner 通常专用账户),但一旦发生是静默退化、运维无信号可感知。
可选修复方向
- 钳量成交后让 guard 重新评估:钳量 fill 成功后调
cancel_pending_exit(inst)(需 LiveEngineSession 暴露途径)。代价:session 残差未消时会产生可见 rejected 噪音(但不再永久静默失效)。
- session ← DB reconcile:钳量全平 DB 后把 session 该 inst 也强制归零(对齐
_restore_position 思路),根治残差。改动面更大,需考虑回测 / live 一致性。
两方向需评估对回测==live 行为一致性的影响后再定。
参考
背景
承接 #111(保护性出场遇 session/DB 分叉钳到实仓全平)。该 PR 已确保 DB 实仓被钳量全平、不裸空、止损不再被静默吃掉,但留下一个 session/DB 对账层面的缺口,在 PR 注释里已诚实标注、刻意未塞进该 PR(范围控制)。本 issue 跟进。
问题
钳量场景(前置分叉源自同账户 HTTP
/orders/submit卖单)下:LiveEngineSession.confirm_fill按钳后量 0.5 增量减仓 → session portfolio 1.0 → 0.5(残留分叉:session 0.5 vs DB 0)。PositionGuard首次触发时已把该 inst 记入_pending_exit_insts;confirm_fill不清它(只有reject_order的保护性单分支、或pos转 flat 才清,见position_guard.evaluate)。guard.evaluate命中 pending 去重 → 对该 inst 静默 skip、不再发出场单。影响
guard因_pending_exit_insts未清仍跳过 → 新建持仓得不到 PositionGuard 灾难止损保护,直到 run restart / reconcile。可选修复方向
cancel_pending_exit(inst)(需LiveEngineSession暴露途径)。代价:session 残差未消时会产生可见 rejected 噪音(但不再永久静默失效)。_restore_position思路),根治残差。改动面更大,需考虑回测 / live 一致性。两方向需评估对回测==live 行为一致性的影响后再定。
参考
services/paper/src/inalpha_paper/live_runner.py_route_through_plan_exec的「回灌 session」段services/paper/src/inalpha_paper/engine/position_guard.pyevaluate/_pending_exit_insts