Skip to content

fix(paper): PositionGuard 加固集成(承接 #88 全部 CR 修复)#93

Open
mirror29 wants to merge 1 commit into
mainfrom
feat/position-guard-hardening
Open

fix(paper): PositionGuard 加固集成(承接 #88 全部 CR 修复)#93
mirror29 wants to merge 1 commit into
mainfrom
feat/position-guard-hardening

Conversation

@mirror29

Copy link
Copy Markdown
Owner

背景

main 经 d12 合并已含早期版 PositionGuard(ADR-0052),但缺了 #88 七轮 CR 的加固——其中两条是真风控缺口。本 PR 是针对当前 main 的纯加固 delta(不重复已在 main 的基础实现),把 #88 的修复整合进来。#88 因 base 被 main 取代、平行实现冲突已关闭。

修的什么(main 现状 → 本 PR)

风控(major)

  • is_protective_order 从单因子(只看 order.tag)→ 三因子(side==SELL + tag ∈ 保护集 + client_order_idguard- 前缀)。否则策略写 Order(tag="stop_loss") 即可仿冒、绕过 enforce(熔断锁期内继续开仓)
  • 保护性出场同时豁免 check_order_notional:价涨/累积建仓后止损 SELL 的 notional 必然超单笔上限,原先会被拒 → 持仓不动 → 每根 bar 重试又被拒 → 止损静默失效

观测(major)

  • report.protective_exits 从按 tag 计数 → 按 FillRecord.is_guard(成交侧三因子算出)。否则策略自带 tag="stop_loss" 会被混入 → agent 误报"框架止损 N 次"

架构 / 一致性 / 其它

  • PROTECTIVE_EXIT_TAGS / GUARD_ORDER_PREFIX / is_protective_* 移到 model.orders 中立层 → 消除 engine↔execution 循环依赖
  • 末根 bar 触发的保护单收尾按 close 兜底成交(flush_protective_at_close),对齐 live
  • trailing 改自峰值价格回撤口径(非成本收益率降幅)
  • add_strategy 启用 guard 时多策略显式 RuntimeError;live 出单日志区分 guard/策略
  • TS BacktestReport.protective_exits 改非 optional

保留 main 的内容

合并保留了 main 的 SharpeCI 响应映射、report 的 bootstrap CI、以及 main 自己的同 bar 双 SELL 回归测试——本 PR 只叠加加固,不回退 main 的工作。

验证

🤖 Generated with Claude Code

main 经 d12 已并入早期 PositionGuard,但缺 #88 七轮 CR 的加固。本次把缺的补齐:
- 风控(major):风控豁免改 is_protective_order 三因子(side=SELL+tag+guard 前缀),
  防策略 Order(tag="stop_loss") 仿冒绕 enforce;保护性出场同时豁免 notional 硬上限
  (价涨/累积建仓后止损 SELL 必超单笔上限,否则止损静默失效)
- 观测(major):report.protective_exits 改按 FillRecord.is_guard 计数,不再混入策略
  自带 stop_loss tag(成交侧 is_protective_signature 算出 is_guard)
- 架构:PROTECTIVE_EXIT_TAGS/GUARD_ORDER_PREFIX/is_protective_* 移到 model.orders 中立层,
  消除 engine↔execution 循环依赖
- 一致性:末根 bar 触发的保护单收尾按 close 兜底成交(flush_protective_at_close),对齐 live
- trailing 改自峰值【价格】回撤口径(非成本收益率降幅)
- add_strategy 启用 guard 时多策略显式 RuntimeError;live 出单日志区分 guard/策略
- TS BacktestReport.protective_exits 改非 optional
- 测试:并入 #88 全部回归(三因子仿冒/BUY 仿冒/is_guard 计数/末根/多策略)+ 保留 main
  的同 bar 双 SELL 回归;保留 main 的 SharpeCI 响应与 bootstrap CI

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying inalpha-web with  Cloudflare Pages  Cloudflare Pages

Latest commit: ad4d837
Status: ✅  Deploy successful!
Preview URL: https://08d33368.inalpha-web.pages.dev
Branch Preview URL: https://feat-position-guard-hardenin.inalpha-web.pages.dev

View logs

@claude

claude Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Review @ ad4d837

总结

对照 CLAUDE.md 硬约束和通用工程维度逐一检查,核心修复逻辑正确,测试覆盖充分(762 passed),没有发现 critical/major 级别问题。以下是完整的评审记录。


必修(critical / major)

无。


可选优化(medium)

1. exchange.py:flush_protective_at_close — 被 can_afford_sell 挡住的保护单静默遗留

flush_protective_at_close 里,若 SELL 量 > 当前持仓(can_afford_sell 返回 False),该订单留在 remaining,但回测结束后不会再有 process_bar,等同于静默丢弃。正常路径下不可能发生(guard 出场量 = pos.quantity,guard evaluate 与 flush 之间没有成交改变持仓),属防御性代码。

建议:不需要改逻辑,但可以在 if > 0: ... 同层加一行 assert self.exchange.pending_count() == 0 or ...,或在 flush_protective_at_close 里对 can_afford_sell 失败路径加 _logger.warning,方便未来调试时确认边界。

2. position_guard.py:_triggered_tag:170peak_mark > 0 冗余守卫

and peak_mark > avg   # avg > 0 已在 evaluate() L137 守门
and peak_mark > 0     # 因 peak_mark >= mark >= avg > 0,此条件恒 True

peak_mark > 0 永远为 True(正常价格且 avg > 0 已守门),是死代码。不影响行为,删掉可减少读者困惑。


逐维度核查记录

正确性

  • 三因子判定 (side==SELL + tagPROTECTIVE_EXIT_TAGS + client_order_idguard- 开头):is_protective_signaturemodel.orders 中立层实现,portfolio 侧取 OrderFilled.tag(通过 ExecutionEngine 的 tag=order.tag 透传,L125 execution_engine.py)+ OrderFilled.client_order_id,字段来源可靠,三因子均正确传递。
  • _peak_mark 生命周期:guard 触发后 pop 清除,下一 bar process_bar 成交让持仓 flat,evaluate 看到 pos.is_flat 再次 pop(no-op),不会重复触发或状态泄漏。
  • 末根兜底 ts 一致性flush_protective_at_closebar.ts_event(末根 bar)作填充时间戳,portfolio.snapshot(bars_list[-1].ts_event) 覆盖同一 ts 的末点(snapshot L186 同 ts overwrite 逻辑已验证),不产生重复时间点或多算 bar。
  • trailing 口径(peak_mark - mark) / peak_mark >= threshold,改前是 peak_pct - pct >= threshold(回报率口径)。新口径避免大浮盈下触发阈值偏紧的问题。peak_mark > avg 守门(仓位曾进盈利区)与旧的 peak > 0 语义等价但更清晰。

设计 / 架构

  • PROTECTIVE_EXIT_TAGS / GUARD_ORDER_PREFIXengine/position_guard.py 移到 model/orders.py,消除了 engine↔execution 之间的循环依赖,方向正确。
  • _publish_fill 提取为私有方法,process_barflush_protective_at_close 共用,逻辑不重复。

契约 / 兼容

  • BacktestReport.protective_exits?: numberprotective_exits: number(非 optional):Python 侧 schemas.py:337default=0,API 响应恒带该字段,TS 去掉 ? 是正确收紧,不破坏调用方。
  • add_strategy 新增 RuntimeError(guard 启用 + 已有策略时):是主动限制而非退化,breaking change 有意为之,文档已写明。

安全

  • 代码注释已明确:三因子不是对抗性防伪(策略可控所有字段),真正防伪需沙箱/AST 审计(ADR-0052 已知攻击面)。这是设计决策,当前 PR 范围内可接受。
  • is_protective_order 挡住 BUY 单借用豁免的路径(side==SELL 守门)。

状态 / 数据流

  • flush_protective_at_close 只在 guard 触发后(> 0)才重刷 snapshot,不影响正常回测路径。
  • on_stop() 在 flush 之后调用,策略已经通过 msgbus 收到 PositionClosed_publish_fill → ExecutionEngine → Portfolio → msgbus 同步回调),on_stop 时持仓状态正确。

可测性

新增 5 个聚焦测试:末根触发 / guard 拒第二策略 / 多策略无 guard / 策略自带 tag 不计入 guard 计数 / 三因子单元测试。覆盖了所有新引入的边界。


🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant