Files
tick-stock-panel/docs/auto-strategy-lab-plan.md
T
shy3130 21279ecfa2 feat(lab): 自动策略实验室 — 守护复验 + 稳健选冠/权重精调 + 组合建议 + 闭环编排
在既有挖掘链路之上补齐「发布后持续验证与持续组合」闭环
(设计: docs/auto-strategy-lab-plan.md):

- P1 策略守护: 挖掘发布自动落 mining_gate 基线 (天然 OOS); 每日收盘
  数据更新后在新数据上切 K 段真实回测复验, 按稳健分对比基线判定
  健康/观察/退役三态 (段回测失败只降级观察不退役; 新数据 <30 交易日
  跳过本轮; retired 不再自动复验)。任意策略可手动入护 (尾随参考基线,
  判定只针对入护后新数据)。回测页稳健性验证新增「策略守护」子页。
- P2a 稳健选冠: 冠军排序分从外层折 Sharpe 均值改为 0.6×中位+0.4×最差
  (复用 robust_score); artifact 新增可选列 oos_worst_sharpe (v2 旧档
  兼容读取); strict 档发布额外要求最差折 Sharpe>0; 算法版本升
  mining-v3 并进数据指纹, 防止升级后 claim 幂等复用旧口径结果。
- P2b 权重精调: 冠军组合在 {0.5,1.0,1.5,2.0} 比率网格 (首因子固定
  1.0, 截面分位打分天然尺度不变) 两段式精调 — 外层测试窗选优 + 全窗
  复验退化>20% 放弃; 消耗 run 剩余预算 (不足自动跳过); 产物为独立
  签名候选行, run summary 带 refinement 摘要。
- P2d 组合建议: 守护运行时扫描健康冠军间近 126 个交易日日收益 Pearson
  相关性 (≥30 共同交易日, ≤0.7 入选); 前端人工确认后生成等权
  composite 策略 (fail-closed 建议对校验 + blake2b 幂等) 并自动以
  尾随参考基线入护。
- P3 闭环编排: lab_mined_slot_limit 偏好 (默认 10, 0=不限) 在发布
  门槛通过后校验, 名额满则新冠军停在 pending 等位;
  /api/backtest/lab/report 聚合守护/挖掘/名额战报 (前端总览卡);
  lab_schedule 单入口编排周度挖掘 + 日度守护 (失败互不阻断)。

测试: 新增 guardian 判定/运行时/存储/调度/API/组合与挖掘 v3 选冠/
精调用例; 后端全量 3224 通过, 前端 308 通过; 真实数据沙盒 E2E
(挖掘 v3 全链路 / 发布门槛 / 名额边界 / 复验语义 / 组合扫描 /
战报核对 / 性能延迟) 通过。P2c (overlay 候选) 按设计稿按需启动,
默认不做。

Signed-off-by: shy3130 <shy3130@qq.com>
2026-10-25 23:43:34 +08:00

15 KiB
Raw Blame History

自动策略实验室设计(挖掘 → 组合 → 持续验证闭环)

状态:P1(策略守护)、P2a(稳健选冠)、P2b(权重精调)、P2d(策略级组合)、P3(闭环编排)均已实施并通过测试与真实数据 E2E 验证;P2c(overlay 候选)按 §4.3 约定"按需启动,默认不做"暂不实施(需扩展可信发布契约,本轮收益/风险比不划算)。本文回答一个需求:自动挖掘并验证各种因子,组合成好用的策略,并持续组合、持续验证。

落地口径与设计稿的差异(均已在代码与测试中体现):

  • P2b:权重网格固定首个因子为 1.0(截面分位打分天然尺度不变),实际枚举 {0.5, 1.0, 1.5, 2.0}^(n-1)(≤64 变体);stage1 复用 run 已备矩阵跑外层测试窗口,stage2 全窗口复验退化 >20% 放弃;精调预算 = run 剩余额度(上限 96),预算不足自动跳过(4 因子冠军在 balanced 档默认跳过属预期)。精调结果作为新候选行追加(独立签名),不覆盖原冠军。
  • P2d:相关性采用日收益 Pearson(≥30 个共同交易日,样本更多);建议对相关性阈值 ≤0.70;生成 composite 需人工在前端确认(fail-closed:不在最新建议中的对直接拒绝),幂等(blake2b 签名),自动以 trailing_reference 基线入护。
  • P3:lab_schedule.run_scheduled_lab 为调度单入口(周度挖掘 + 日度守护,失败互不阻断);名额满时新冠军停在 pending 候选等位;retired 不自动删文件,腾名额走人工归档/删除既有路径。

结论先行:仓库已存在"因子池统计筛选 → 束搜索组合 → 嵌套样本外验证 → 晋升门槛 → 一键发布策略"的完整主链路(mining-v2,见 docs/mining.md)。本设计不重建挖掘引擎,而是补三块真正的缺口:

  1. P1 策略守护:发布后的策略在新数据上持续复验,退化自动告警/标记退役——"持续验证"目前完全不存在。
  2. P2 组合进化:冠军选择从"单点 OOS 均值"升级为稳健分(median+worst),并对冠军组合做权重精调(复用稳健优化器)——"组合"目前只有 1.0/2.0 离散权重的束搜索。
  3. P3 闭环编排:把周度挖掘与每日守护合并为一个有名额上限、有战报的总控状态机——"持续组合并验证"的自动循环。

一、现状盘点(均为已实现,设计以此为基础)

能力 位置 说明
因子池统计筛选 backend/app/services/auto_mining.py screen_all_factors() 按 |IC|×|IR| 选因子池
束搜索组合 backend/app/backtest/mining.py beam_search_factor_combinations() (L610) 组合 ≤4 因子,权重只试全 1.0 或单因子 2.0
嵌套样本外验证 mining.py generate_nested_folds() (L752) 外层 504/126/63,内层 252/63/63,purge 30 + embargo 5
真实回测评估 backend/app/backtest/mining_runtime.py MatcherCandidateEvaluator (L142) 候选转 factor_rank_research 策略真跑回测,score=外层 OOS Sharpe
晋升门槛 mining.py evaluate_candidate_gate() (L198) 折数/正收益比/Sharpe≥0.5/回撤≥-25%/交易数≥60/confidence
发布成策略 backend/app/services/mining_candidates.py publish() (L112) 渲染 mined_factor_*.py,不记录发布基线
周度定时挖掘 backend/app/services/mining_schedule.py run_weekly_mining() (L126) 数据指纹 + claim 防重
进程隔离与预算 mining_runtime.py + worker.py + MiningRunStore spawn 子进程,峰值 RSS ≤1.5GiB(实测 ~1.35GiB/41s)
稳健优化基建 robust_optimizer.py / walkforward.py / guards.py robust_score=0.6×median+0.4×worst、K 段切分、目标白名单
评估器注入点 mining.py CandidateEvaluator Protocol (L426) MiningService.run(..., evaluator=) 可整体替换评估语义

缺口(本设计要解决的):

  • 发布即终点:publish() 之后策略与普通策略无异,因子失效无人知晓。
  • 选冠不稳健:外层折 Sharpe 取均值,单折运气可登顶;与稳健优化器的 median+worst 语义割裂。
  • 组合空间窄:权重 {1.0, 2.0}、无 overlay/风控维度、无策略级组合(composite 已有引擎但无自动流程)。

二、总体结构

                ┌─────────────── P3 总控(名额上限 + 战报)───────────────┐
                │                                                        │
  周度:  screen_all_factors ──> MiningService 束搜索 ──> gate ──> publish ──┐
  (已有)  (因子池筛选)          (P2a: 稳健选冠)      (P2b: 权重精调)        │
                                                                          v
  每日:                                              策略守护档案 (基线 + 复验历史)
  (P1 新建)                                           healthy / watch / retired
                                                          │
                                            退化 > 阈值 ──┘──> 告警 + 标记退役(腾名额)

数据流纪律(关键):守护层(发布后数据)绝不反馈进取参加权。P2 的精调只用挖掘窗口内的 K 段锚定;发布后数据只用于验证与退役判定。这是逐层 OOS:挖掘嵌套验证 → 晋升门槛 → 发布后纯 OOS 追踪。


三、P1 策略守护(持续验证)

3.1 数据模型

  • 守护档案:data/user_data/strategy_guardian/{strategy_id}.json,每策略一份:
    • baseline:发布/入护时的指标快照(objective 分、max_drawdown、n_trades、valid_folds、data_as_of、definition 指纹)。
    • baseline_kind:mining_gate(发布时来自晋升门槛,天然 OOS)或 trailing_reference(手动入护策略,取入护日前的尾随窗口作参考——不是 OOS,仅作退化比较基准)。
    • checks[]:历次复验记录(≤52 条滚动),每条含窗口、各段分数、robust 分、退化百分比、判定。
    • status:healthy | watch | retired,及进入当前状态的时间与原因。
  • 复验 run 工件:data/research/guardian/runs/{run_id}/,沿用 MiningRunStore 的 manifest/summary/events 模式(重启可恢复、SSE 可续传)。

3.2 基线写入

  • MiningCandidateService.publish() 渲染 mined_factor_* 成功后,同步写守护档案(baseline 来自 gate 指标,baseline_kind=mining_gate)。失败不影响发布本身(守护是旁路,fail-open 于发布、fail-closed 于判定)。
  • 手动入护:POST /api/backtest/guardian/{strategy_id}/adopt,任意策略可入护;首次先算尾随参考基线,标记 trailing_reference。

3.3 复验算法(每策略每次)

  1. 窗口:baseline.data_as_of 之后的全部新交易日,用 robust_optimizer.split_validation_windows 切 K 段(K=2..4,每段 ≥14 交易日);新增数据 <30 交易日则本轮跳过(记录 skipped)。
  2. 每段一次真实回测:StrategyBacktestService.run + BacktestResultPolicy.optimizer_trial(复用 mining_runtime 的矩阵共享与裁剪模式),objective 默认 sharpe(与挖掘 gate 同口径),必须属于 guards.PRIMARY_OBJECTIVES。
  3. 当前分 = robust_optimizer.robust_score(0.6×median + 0.4×worst)。
  4. 退化判定(方向感知,公式与 robust_optimizer stage2 的 degradation 同语义,min 类目标不误判):
    • healthy:退化 ≤20% 且软底线未击穿(sharpe>0、回撤不差于 -30%、n_trades ≥ 基线 1/3)。
    • watch:退化 20%–50%,或击穿一项软底线;连续 2 次 watch 触发通知。
    • retired:退化 >50%,或硬底线击穿(n_trades 枯竭到接近零——因子失效最常见的形态是"不再触发")。
  5. 退役动作只改档案状态 + 系统通知 + 前端置灰,不自动删除/移动策略文件(用户数据安全,CONTRIBUTING.md §8);提供人工"归档"按钮,走策略删除的既有 fail-closed 校验路径。retired 不自动回场。

3.4 执行模型

  • 新 worker kind "guardian":worker.py make_worker_task (L129) 加一个分支,子进程内逐策略复验(全市场矩阵一次 prepare_matrix_optimization,各策略按需重建——策略 universe/评分不同,矩阵不能跨策略复用,但同策略的 K 段用 market_data_override 只读视图复用,模式同稳健优化器)。
  • 并发槽:与挖掘共用 shared_heavy_job_limiter.slot("mining")——两者都是全市场级内存任务,串行化避免双份 1.3GiB 矩阵。
  • 调度:guardian_schedule.run_daily_guardian(),镜像 mining_schedule 的 claim 模式(claim id guardian-daily-{date},enriched 数据指纹无变化则跳过)。挂在主进程现有后台调度线程上。
  • task_registry.TASK_KINDS 增加 "guardian"(前端 taskRegistry.ts 同步)。

3.5 API 与前端

  • backend/app/api/guardian.py:GET /api/backtest/guardian/overview(全部守护策略:状态、基线 vs 最近、退化、曲线端点)、POST /{sid}/adopt|drop|rerun、GET|PATCH /config(阈值、objective、K、开关走 preferences)。
  • 前端:回测页"稳健性"域新增子 tab 守护 → frontend/src/pages/backtest/StrategyGuardian.tsx(RobustnessValidation.tsx 加一个子 tab,路由不动)。表格 + 状态徽章 + 基线/近期对比 + 各段分数;rerun 走轮询(同 miningTask 模式),不需要 SSE 镜像。

四、P2 组合进化

4.1 稳健选冠(2a)

  • mining_runtime 内给 MatcherCandidateEvaluator 的外层聚合换语义:冠军排序分从"外层折 Sharpe 均值"改为 robust_score(各外层折 OOS sharpe)(median+worst),并把逐折分散度(worst/best)写入候选 meta。
  • 晋升门槛增加可选稳健维度:strict 档要求 worst-fold sharpe > 0(默认开);gate 的绝对阈值不变。
  • 版本纪律:选冠语义变化会改变同请求的结果,落地时 MINING_ALGORITHM_VERSION 升为 mining-v3,并进数据指纹,防止升级后 claim 幂等复用旧口径结果。

4.2 冠军权重精调(2b)

  • 对束搜索最终冠军组合(≤4 因子),权重在 {0.5, 1.0, 1.5, 2.0}^n(≤256 组合)网格上做 K 段锚定验证(完全复用 RobustOptimizeService 的两段式:stage1 全网格分段,stage2 Top-N 全窗口复验退化),选出稳健权重写入发布策略的 scoring。
  • 矩阵复用挖掘 run 已准备的全区间超集矩阵(同策略/universe/区间,签名校验天然通过),精调增量成本 ≈ 一次稳健优化的 stage1。
  • 预算:精调 trial 计入 MiningBudget,不另开上限。

4.3 维度扩展(2c/2d,按需启动,默认不做)

  • 2c overlay 候选:对最终冠军试 ≤4 个预定义 overlay 模板(排除高位、排除低流动性等)。注意 overlay 改变 universe,每个变体需重建矩阵,成本 ×变体数,只在 strict 档开放。
  • 2d 策略级组合:守护档案中 ≥2 个 healthy 冠军且日收益相关性 ≤0.7(新增策略收益相关性计算,因子相关性剪枝 compute_rank_correlation 不直接适用)时,前端提示"可组合",人工确认后生成 composite 策略(引擎已支持 children ≤8)并纳入守护。不自动生成,防组合爆炸与过拟合叠加。

五、P3 闭环编排

  • lab_schedule.py 统一状态机:idle → screen → mine → gate → publish → watch → retire(腾名额) → idle。两个 claim:lab-weekly-{iso}(挖掘+精调)、lab-daily-{date}(守护复验),替代原 mining_schedule 入口(保留旧入口兼容一个版本)。
  • 名额上限:活跃 mined 策略数 preference(默认 10);名额满时不发布新冠军,产出 pending 候选等位。
  • 战报:守护 + 挖掘 summary 聚合为前端总览卡(本周新挖掘 run、晋升/退役/在役数、平均退化、最弱在役策略)。

六、关键设计决策与风险

决策 理由
守护层数据绝不反馈进参数选择 逐层 OOS 纪律;否则"用未来调参",守护退化判定失去意义
退役不自动删策略文件 用户数据安全;删除/归档是高风险操作,必须人工确认
手动入护基线标 trailing_reference 尾随窗口不是 OOS,语义必须显式区分,否则误判退化
guardian 与 mining 共用 heavy 槽 两个全市场矩阵并发会破 RSS 门槛;串行化是唯一安全选项
2a 落地必升算法版本号 同请求不同口径的结果混用 claim 幂等缓存是数据事故
objective 只取 guards.PRIMARY_OBJECTIVES win_rate 等退化目标做主目标是已知反模式
复验窗口 <30 交易日跳过 段太短的 robust 分是噪声,判定只会制造假告警
generation 快照 + 重试读面板 沿用挖掘运行时既有纪律,撞上 enriched 发布 fail-closed

剩余风险:守护判定阈值(20%/50%)是经验值,需在 P1 上线后按实际分布校准;策略收益相关性(2d)已定为日收益 Pearson(≥30 共同交易日)。


七、测试矩阵

改动 最低验证
判定状态机(纯函数) healthy/watch/retired 边界、方向感知(min 目标)、n_trades 枯竭、退化百分比边界
基线写入 publish 后档案存在且指标与 gate 一致;发布失败不写档案;手动入护标 trailing_reference
调度 claim 同日防重、数据指纹不变跳过、新增数据 <30 日跳过
worker/API kind 注册、overview/adopt/drop/rerun 契约与错误路径、generation fail-closed
2a 选冠 median+worst 与均值排序产生不同冠军的构造用例;版本号 bump 断言
2b 精调 网格 ≤256、与 robust_score 复用一致性、预算计入
前端 pnpm build;守护页加载/空/错/watch/retired 状态
回归 test_mining_*.py 全量 + 新增 test_strategy_guardian*.py

八、分期与验收

  • P1(先行):发布基线自动落档 + 每日复验 + 三态判定 + 守护页。验收:对一个已发布 mined 策略跑通"发布→复验→人工注入退化数据→watch/retired 告警"全链路(测试内构造),真实数据下守护页正常展示。✅ 已实施并通过真实数据 E2E。
  • P2a+2b:稳健选冠 + 权重精调 + 版本号升级。验收:同一历史 run 请求在新旧口径下冠军可不同且均有记录;精调后发布策略的 scoring 与档案一致。✅ 已实施(mining-v3,精调差异见文首落地口径)。
  • P2d、P3:✅ 已实施(组合建议扫描/人工确认生成 composite + 自动入护;lab 调度单入口 + 名额上限 + 战报聚合 /api/backtest/lab/report + 守护页战报卡)。
  • P2c:按需启动,默认不做(见文首说明)。