Backtest 失败后的标准处置 SOP

文档目的:为量化研究线在 05_backtest 结果不理想时,提供一套可审计、可复现、可治理的标准处置流程,避免“无方向优化”“回头调参”“静默改口径”。

适用范围:适用于具备明确谱系与阶段治理的研究线,尤其适用于如下 stage workflow:

  • 00_mandate
  • 01_data_ready
  • 02_signal_ready
  • 03_train_freeze
  • 04_test_evidence
  • 05_backtest

适用对象:Research、Quant Dev、Reviewer、PM / Risk / Model Governance。


1. 核心原则

1.1 Backtest 是“交易可行性审计层”,不是“反向发明信号层”

05_backtest 的职责是验证:

  • 上游已冻结的 Universe、参数、阈值、信号与执行规则,
  • 在真实费用、真实持仓语义、真实容量假设下,
  • 是否具备可交易性。

不负责

  • 看到结果差后反向重写主信号;
  • 回头重估 best_h、whitelist、train/test cut;
  • 用账户级结果去倒推 signal formula。

1.2 失败先冻结,再解释

任何不理想 backtest,第一动作都不是“优化”,而是:

  1. 冻结本次失败结果;
  2. 做失败分类;
  3. 明确 rollback stage;
  4. 写 allowed / forbidden modifications;
  5. 再决定是 RETRYRESEARCH_AGAINNO_GO 还是 CHILD_LINEAGE

1.3 只能在“失败所属层”修改

  • 工程问题,在工程层修;
  • 执行问题,在执行层修;
  • 研究证据问题,回到 03/04 修;
  • 主问题变了,开新谱系。

严禁跨层补丁式优化。

1.4 少量、受控、可解释的 retry

每次 retry 必须满足:

  • 有且仅有一个明确假设;
  • 有且仅有一组受控修改;
  • 有明确预期改善指标;
  • 同一失败类型的 controlled retry 不得无限持续。

建议纪律:同一失败类型最多 1–2 次 controlled retry;再无改善,则升级为 RESEARCH_AGAINNO_GOCHILD_LINEAGE


2. 失败触发条件

满足以下任一条件时,应触发本 SOP:

  • 05_backtest 账户级 net_pnl_sum <= 0,或 total_return <= 0
  • 毛利为正但净利为负,且费用显著吞噬 alpha;
  • 回撤、容量、换手、滑点表现明显超出项目容忍区间;
  • 双引擎语义不一致、复算不一致、结果不可复现;
  • 样本外 / holdout / paper trading 与预期偏离过大;
  • 结果只能在狭窄参数点成立,缺乏稳健性。

3. 标准流程总览

步骤 0:冻结失败结果(Failure Freeze)

步骤 1:正确性排查(Correctness Triage)

步骤 2:统计证据排查(Evidence Triage)

步骤 3:经济性与执行排查(Economics / Execution Triage)

步骤 4:主假设复审(Thesis Review)

步骤 5:决策分流(Decision Fork)

步骤 6:受控回退或停线(Rollback / Stop / New Lineage)


4. 步骤 0:冻结失败结果

4.1 必须冻结的内容

一旦 backtest 失败,必须生成 failure package,至少包括:

  • lineage_id
  • run_id
  • 代码版本 / commit hash
  • 数据版本 / 数据时间范围
  • Universe 版本
  • 参数版本
  • 时间切分方式
  • 手续费 / 滑点 / 借券 / 资金规模假设
  • 撮合语义 / 引擎版本
  • 账户级关键结果
  • stage aggregate 结果
  • 已知异常与未决问题

4.2 推荐产物

建议固定输出以下文件:

  1. failure_freeze.md
  2. repro_manifest.json
  3. failure_classification.md
  4. rollback_decision.yaml
  5. retry_plan.md(仅当决策为 RETRY 时)

4.3 Failure Freeze 最小模板

lineage_id: <lineage>
run_id: <run>
stage: 05_backtest
status: failed_frozen
code_version: <git_sha>
data_version: <data_tag>
universe_version: <universe_tag>
parameter_version: <param_tag>
execution_semantics: <description>
fee_model: <description>
slippage_model: <description>
capital_assumption: <description>
key_results:
  gross_pnl_sum: ...
  fee_paid_sum: ...
  net_pnl_sum: ...
  total_return: ...
  max_drawdown: ...
  turnover: ...
known_issues:
  - ...
open_questions:
  - ...

5. 步骤 1:正确性排查(Correctness Triage)

这一步先回答“结果对不对”,而不是“结果赚不赚”。

5.1 检查清单

A. 可复现性

  • 同一配置重跑,结果是否一致;
  • 固定种子 / 固定输入后,输出是否稳定;
  • 关键汇总文件与逐笔成交是否一致。

B. 双引擎 / 交叉引擎一致性

  • 至少使用两套回测或执行语义进行交叉验证;
  • 检查 semantic_gap 或等价字段;
  • 若两引擎结果方向相反或偏差巨大,优先归类为工程问题。

C. 时间与因果关系

  • 信号是否使用了未来信息;
  • T 信号与 T+1 收益是否正确对齐;
  • 标准化窗口、回归窗口、持仓窗口是否存在泄漏。

D. 数据质量

  • 对齐是否正确;
  • 缺失、停牌、异常值、复权、退市、可交易性过滤是否正确;
  • Pair / spread / hedge leg 的单位是否一致。

E. 执行状态机正确性

  • entry / exit / stop / cancel 的状态机是否正确;
  • cross_only 是否被实现成 stateful cross_only
  • max_holdstopmean_reached 等 close reason 是否符合合同。

F. 成本与单位边界

  • stage aggregate 的单位是否与账户级单位明确区分;
  • 手续费是否按真实名义仓位计算;
  • 单腿 / 双腿费用是否与 contract 一致。

5.2 判定标准

若此步发现:

  • 结果不可复现;
  • 双引擎语义不一致;
  • 存在未来函数;
  • 数据错位;
  • 单位换算错误;
  • 成本模型实现错误;

则归类为:

ENG_FAIL

5.3 ENG_FAIL 处理规则

  • rollback_stage:缺陷所在 stage(通常为 01_data_ready02_signal_ready05_backtest 实现层)
  • 决策RETRY
  • 允许修改:数据链路、状态机、撮合逻辑、费用逻辑、实现 bug
  • 禁止修改:Universe、信号表达式、上游冻结对象、研究主问题

6. 步骤 2:统计证据排查(Evidence Triage)

只有 correctness 通过后,才检查“信号统计上到底还在不在”。

6.1 检查目标

  • 04_test_evidence 的核心结论是否真的稳健;
  • 当前 backtest 失败,是不是 earlier-stage 证据本来就弱,只是到 05 才暴露;
  • 是否存在严重 multiple testing / data snooping;
  • 上游 freeze discipline 是否被破坏。

6.2 检查清单

A. 结构证据是否稳定

  • 主假设对应的 test metrics 是否在 test / holdout 上稳定;
  • 不是只在 train 或少量片段上成立;
  • 不是只靠极端样本支撑。

B. 样本外纪律是否被破坏

  • 阈值、白名单、best_h 是否只用 train 冻结;
  • backtest 阶段是否出现“看了结果再回头改 freeze 对象”的行为;
  • 是否在同一份 OOS 上反复调参。

C. 稳健性

  • 相邻参数是否不是断崖式失效;
  • 不同时间切片、不同 regime、不同 symbol bucket 是否大致一致;
  • 不应只有一个窄点好看。

D. 研究问题是否发生漂移

  • 当前线是否还在回答原 Mandate 的主问题;
  • 还是已经变成另一个问题,只是名字没换。

6.3 判定标准

若此步发现:

  • 04_test_evidence 本身就不稳;
  • 样本外纪律被破坏;
  • best_h、whitelist、threshold 在 05 被反向重估;
  • backtest 的失败实质上是 research evidence 的失败;

则归类为:

RESEARCH_FAIL

6.4 RESEARCH_FAIL 处理规则

  • rollback_stage03_train_freeze04_test_evidence
  • 决策RESEARCH_AGAIN
  • 允许修改:研究设计、证据口径、切片验证、稳健性验证、freeze discipline
  • 禁止修改:在同一份 backtest / OOS 上继续调参直到好看

7. 步骤 3:经济性与执行排查(Economics / Execution Triage)

若 correctness 正确、统计上仍有一定边,则进入经济性与执行层排查。

7.1 先拆 gross 与 net,不要先看总收益

至少拆以下指标:

  • gross_pnl_sum
  • fee_paid_sum
  • slippage_sum
  • net_pnl_sum
  • gross/trade
  • fee/trade
  • net/trade
  • turnover
  • 平均持仓时长
  • close reason 分布
  • 容量 / 资金规模敏感性

7.2 重点检查项

A. 信号有边,但太薄

典型症状:

  • gross > 0
  • net < 0
  • gross/trade 明显小于 fee/trade

B. 换手过高

典型症状:

  • 交易次数很多;
  • 单笔利润很小;
  • 零成本或低成本表现尚可,但真实费率下崩掉。

C. 持仓规则没有在消费主信号语义

例如:

  • 研究主张是 mean reversion;
  • 但实盘执行几乎全部由 max_hold 退出;
  • 实际上变成了“事件 + 固定持有期”而不是“回归即退出”。

D. 规模 / 容量不匹配

例如:

  • 小 alpha 在小资金下可活;
  • 在正式 pair budget 下被成本吃掉;
  • 策略并非完全无效,而是在当前成本 / 容量假设下不可实施。

7.3 判定标准

若此步发现:

  • 信号方向基本正确或略有边;
  • 但交易成本、滑点、容量、换手、持有期规则吃掉了收益;
  • 或当前执行语义明显偏离研究语义;

则归类为:

EXEC_FAIL

7.4 EXEC_FAIL 处理规则

  • rollback_stage:通常仍为 05_backtest,必要时回退到 execution contract 设计层
  • 决策RETRY
  • 允许修改
    • exit 语义
    • entry 触发节奏
    • cooldown / re-entry suppression
    • 持仓时长 / stop / take-profit 映射
    • 成本包络测试
    • 资金规模 / 容量分析
    • 订单执行与撮合假设
  • 禁止修改
    • topicA whitelist
    • best_h 重估
    • train/test 切分
    • 因 backtest 结果不好而回头改主信号
    • 将对侧信号静默加回
    • 事后增加 gate 美化收益

8. 步骤 4:主假设复审(Thesis Review)

当 correctness 正确、实现无误、执行与成本也不能解释全部失败时,要回到更高层:

8.1 问四个问题

  1. 这个策略赚的是什么钱?
  2. 这套机制是否仍然存在?
  3. 它是不是只在狭窄历史片段上成立?
  4. 市场结构、费用结构、拥挤度是否已经使原假设失效?

8.2 典型症状

  • 不同时间段都不稳;
  • 稍微改参数就断崖失效;
  • 核心统计证据也弱;
  • 无法用成本或执行规则解释;
  • 研究主问题本身不成立,或已经不再值得投入。

此时归类为:

THESIS_FAIL

8.3 THESIS_FAIL 处理规则

  • rollback_stage:不再 rollback 原线
  • 决策NO_GO
  • 允许动作:停线、记录结论、沉淀负样本经验
  • 禁止动作:继续在原线补丁式优化

9. 步骤 5:决策分流(Decision Fork)

每次失败处理后,只允许输出以下四种正式决策之一:

9.1 RETRY

用于:

  • ENG_FAIL
  • EXEC_FAIL
  • 且问题边界明确,可在受控范围内修复

要求:

  • 写明 rollback stage
  • 写明 allowed modifications
  • 写明 forbidden modifications
  • 写明下一轮唯一核心假设
  • 写明预期改善指标

9.2 RESEARCH_AGAIN

用于:

  • RESEARCH_FAIL
  • backtest 失败只是更晚暴露 earlier-stage 证据不稳

要求:

  • 回到 03/04
  • 重新审视 train/test freeze discipline
  • 重做证据层,而不是继续拿 05 调参

9.3 NO_GO

用于:

  • THESIS_FAIL
  • 或明确判断当前线不值得继续投入

要求:

  • 明确停线理由
  • 记录可复用经验
  • 禁止隐性重启原线

9.4 CHILD_LINEAGE

用于:

  • 研究主问题、交易语义、Universe 合同发生变化
  • 已不再是原题的小修小补

典型例子:

  • 从“双边 OB/OS”转成“只做 3A 超涨回落”;
  • 从“结构诊断”转成“执行 alpha”;
  • 从“Universe only conditioning”转成“逐 bar gate conditioning”。

要求:

  • 新命名
  • 新 mandate
  • 新 contract
  • 与原线清晰隔离

10. 失败分类与回退矩阵

失败类型典型症状rollback stage默认决策允许修改禁止修改
ENG_FAIL不可复现、未来函数、引擎不一致、单位错缺陷所在 stageRETRY数据、实现、状态机、成本逻辑主信号、Universe、freeze 对象
EXEC_FAILgross 有边,net 被成本/换手/退出吃掉05_backtestRETRY执行规则、退出映射、cooldown、容量/成本测试best_h、whitelist、signal formula
RESEARCH_FAIL04_test_evidence 不稳、样本外纪律破坏03/04RESEARCH_AGAIN证据层验证、切片、freeze discipline在同一 OOS 上继续调参
THESIS_FAIL机制不成立、广泛失效、脆弱NO_GO停线、沉淀结论补丁式续命
研究主问题改变研究题目或合同改变新线CHILD_LINEAGE新谱系设计污染原线

11. Retry 的标准写法

每次 retry 只能写成如下结构:

11.1 唯一核心假设

例如:

  • 当前失败主因是纯 hard exit,导致 mean reversion 语义没有被消费。

11.2 受控修改集

例如:

  • 只修改 exit 语义;
  • 不改 signal source;
  • 不改 Universe;
  • 不改 best_h
  • 不回头改白名单。

11.3 预期改善指标

例如:

  • mean_reached 占比上升;
  • 平均持有时长下降;
  • gross/trade 上升;
  • fee/gross 比例下降;
  • 账户级 net_pnl_sum 改善。

11.4 Retry 模板

decision_state: RETRY
failure_class: EXEC_FAIL
rollback_stage: 05_backtest
next_hypothesis: >
  当前失败主因是退出语义将均值回归策略退化为硬持有期事件交易。
allowed_modifications:
  - exit_semantics
  - cooldown
  - reentry_suppression
  - execution_cost_envelope
forbidden_modifications:
  - whitelist
  - best_h_reestimation
  - train_test_resplit
  - signal_formula_rewrite
expected_improvements:
  - higher_mean_reached_ratio
  - lower_turnover
  - higher_gross_per_trade
  - lower_fee_to_gross_ratio

12. 禁止动作清单

以下动作默认视为违规:

  1. 因为 backtest 结果差,回头重估 best_h
  2. 在同一份 OOS / backtest 上反复挑参数直到好看
  3. 05 阶段偷偷更换 Universe
  4. 把对侧信号加回来,掩盖单边策略问题
  5. 用新 gate、美化过滤器掩盖主信号经济性不足
  6. 用 stage aggregate 替代账户级收益结论
  7. 在 summary 文档中混淆单位边界
  8. 不冻结失败版本,直接开始下一轮修改

13. 推荐的最小审计产物

13.1 failure_classification.md

建议固定写:

  • 本次失败属于哪一类
  • 证据是什么
  • 为什么不是其他失败类型
  • rollback 到哪一层
  • 下一步允许改什么、不允许改什么

13.2 rollback_decision.yaml

decision_state: RETRY | RESEARCH_AGAIN | NO_GO | CHILD_LINEAGE
failure_class: ENG_FAIL | EXEC_FAIL | RESEARCH_FAIL | THESIS_FAIL
rollback_stage: 01_data_ready | 02_signal_ready | 03_train_freeze | 04_test_evidence | 05_backtest | none
allowed_modifications:
  - ...
forbidden_modifications:
  - ...
owner: <name>
reviewer: <name>
signoff_required: true

13.3 retry_plan.md

至少包括:

  • 本轮唯一假设
  • 修改项
  • 预期改善指标
  • 成功 / 失败判定标准
  • 最大 retry 次数

14. 将机构常见五阶段流程映射到本 SOP

阶段一:Sanity Checks & Attribution

映射到:

  • 步骤 0:冻结
  • 步骤 1:正确性排查
  • 步骤 3:成本 / 归因 / 容量排查

阶段二:Hypothesis Review

映射到:

  • 步骤 2:统计证据排查
  • 步骤 4:主假设复审

阶段三:Optimization & Refinement

映射到:

  • 仅在 RETRY 条件下受控进行;
  • 必须限定在失败所属层;
  • 不是无限调参。

阶段四:Robustness Testing

映射到:

  • retry 之后必须重新走未触碰样本的验证;
  • 检查参数高原而不是孤峰;
  • 检查 regime / time split / bucket 稳定性。

阶段五:Decision Fork

映射到:

  • RETRY
  • RESEARCH_AGAIN
  • NO_GO
  • CHILD_LINEAGE

15. 套用到你当前项目的落地建议

对当前 topicA -> topicC-3A event_hardexit 这条线,若已确认:

  • 双引擎一致;
  • 不是实现 bug;
  • gross 为正但 net 为负;
  • close reason 几乎全是 max_hold

则当前最合理的正式分类通常应写成:

EXEC_FAIL-primary

建议写法:

  • rollback_stage05_backtest
  • decision_stateRETRY
  • allowed_modifications
    • exit semantics
    • turnover suppression
    • cooldown
    • cost envelope
    • sizing / capacity sensitivity
  • forbidden_modifications
    • topicA whitelist
    • best_h re-estimation
    • train/test resplit
    • OS 重新并入正式执行
    • 事后添加 topicD gate 美化收益

下一轮 retry 的目标不应再写成“继续证明 OB 回落存在”,而应写成:

验证当前失败是否主要由执行退出语义与交易经济学失配导致。


16. 最终结论模板

每次 backtest 失败,最终结论建议固定为以下格式:

16.1 结论摘要

  • 本次失败类型:
  • 主要证据:
  • rollback stage:
  • 决策状态:

16.2 失败归因

  • 是否为工程失败:
  • 是否为研究失败:
  • 是否为执行失败:
  • 是否为主假设失败:

16.3 允许修改 / 禁止修改

  • allowed modifications:
  • forbidden modifications:

16.4 下一步动作

  • RETRY:唯一核心假设与指标
  • RESEARCH_AGAIN:回退到 03/04 的具体任务
  • NO_GO:停线与经验沉淀
  • CHILD_LINEAGE:新线 mandate 的差异点