回测审核清单 · Backtest Pitfalls(研究层)
**结论:engine_v1 全项符合(尾部指标列为存疑)。
回测审核清单 · Backtest Pitfalls(研究层)
这是什么:把 ML4T 教材(
machine-learning-for-trading-main)「回测陷阱」相关章节的操作性要求,转成本库可执行的审核清单——每条都给「可追溯出处 / 表现 / 本库自查命令或字段 / 三态判定」。
第 15/16 条适用范围(2026-10-02 补):第 15 条仅对使用状态空间/卡尔曼类滤波器的新产出生效;
第 16 条仅对新增/修改阈值闸、状态闸的新产出生效(现有闸门为文档定义、门槛明确,不在此列)。
本库
engine_v1(ADX/ER/Hurst 势态)、engine_multi(矩估计)、portfolio.py(60 周样本 std)均不使用此类滤波器,故 §二 的三处机械自查不受第 15 条影响(维持原判:1 违背 + 3 存疑)。怎么用:任何回测产出(新引擎、新表、新图)对外引用前过一遍主表。判定为"违背"的不许在审核中就地修改被审对象,只登记(修复另立任务)。
口径:本清单只描述审核动作,不定义数字口径——数字口径以 框架/数据口径定义 与 回测/perf_audit/README 为准(年化一律
bar 数 / ppy)。出处格式:
来源:16章 §16.7 / notebook 12_dsr_validation(章 = 教材章目录;§ = README 小节;notebook =.py/.ipynb文件名,可ls复核)。
一、清单主表(16 条 · 每条含出处与三态判定)
「本库判定」是对本库现有三处产出(
engine_v1/engine_multi/perf_audit)的汇总三态,明细见 §二。
| # | 陷阱 | 来源(可追溯出处) | 表现(怎么露馅) | 本库自查命令 / 字段 | 本库判定 |
|---|---|---|---|---|---|
| 1 | 前视偏差(look-ahead) | 16章 §16.2 / notebook 01_backtest_first_principles(next-open fill);02章 §2.5 / 14_point_in_time_validation | 信号用了 t 日之后才知道的信息;重采样把期末信息塞到 t | grep -n "shift(|t+1 日生效|无前视" 回测/engine_*/**.py | 符合 |
| 2 | 幸存者偏差 | 02章 §2.5 / notebook 15_survivorship_bias_detection、01_us_equities_eda | 标的池只含"活到今天"的品种;退市/换合约被静默删除 | grep -n "ASSET|资产池|起始年" 回测/engine_multi/*.py;池切换点须显式 | 符合(低风险:标的为指数/现货/ETF) |
| 3 | 数据窥探 / 多重检验 | 16章 §16.7 / notebook 11_sharpe_ratio_inference、12_dsr_validation、13_ras_protocol;07章 §7.4 / 07_multiple_testing | 试了很多参数只报最好那个;把"选出来的夏普"当"真实夏普" | ls 回测/engine_v1/wf/;python3 -c "import json;print(json.load(open('回测/engine_v1/wf/oos_summary.json')))" | 存疑(engine_v1 有折;engine_multi 无滚动 OOS 记账) |
| 4 | 交易成本与滑点(含容量/盈亏平衡换手) | 16章 §16.6 / notebook 14_cost_sensitivity;18章 §18.2 / §18.4 / 12_commission_slippage_comparison、03_market_impact_calibration | 用 0 成本或只算佣金;换手率当成本;无盈亏平衡点 | grep -n "cost_bps|6bps" 回测/engine_*/**.py;tables/D_再平衡x成本矩阵.csv | 符合 |
| 5 | 样本外与参数稳定性 | 16章 §16.7 / 11_sharpe_ratio_inference;06章 / 02_cv_foundations;11章 / 04_nested_cv_hpo | 全样本调参;OOS 与 IS 差距不报;参数对邻域极敏感 | ls 回测/engine_v1/wf/(fold_0..5 + oos_summary) | 存疑(engine_multi 阶段 1 未做滚动 OOS) |
| 6 | 复权 / 分红处理 | 02章 §2.3 / notebook 02_corporate_actions、06_futures_continuous(Panama/ratio 拼接) | 复权价与真实收盘混用;债券用复权价 → 波动系统性低估 | grep -rn "fqt|复权|adj" 回测/ 数据/;框架/数据口径定义 §9.5 | 符合(债券腿 v5.2 已改未复权真实收盘) |
| 7 | 频率与年化口径一致性 | 16章 §16.5 / notebook 09_performance_reporting、07_engine_divergence_anatomy(§16.3) | 年化用日历跨度、波动用 bar 数;两处 ppy 不一致 | grep -n "ppy|365.25|years =" 回测/engine_multi/*.py;回测/perf_audit/README §四 | 违背 → 已修复(v5.3.2)(engine_multi L240 日历跨度 → bar/ppy) |
| 8 | 单一资产 beta 与分散幻觉 | 17章 §17.3 / §17.4;01章 §1.4 / factor_regimes | 把"分散 + beta 选择"当"资产类别分散";两腿高度相关仍称分散 | tables/E_分段相关系数.csv;optimization/报告/决策备忘_核心底仓是否改为金加纳指_2026-09-27 | 存疑(权益腿仅 NDX;换腿仅 2016 起) |
| 9 | 执行时序与成交价 | 16章 §16.2 / 01_backtest_first_principles;§16.3 / 07_engine_divergence_anatomy(逐字段分解成交) | 用收盘价成交却用收盘价定信号;市价单假设无限流动性 | grep -n "next|成交|fill|开盘" 回测/;grep -n "shift(1)" 回测/engine_*/**.py | 符合(t 收盘定信号 → t+1 生效) |
| 10 | 现金约束与隐性杠杆 | 16章 §16.2 / 01_backtest_first_principles(cash constraint、next-open 扣费) | 权重和 > 1 被当满仓;负现金不拦截 | grep -n "1/n|1/3|avg_exposure" 回测/engine_multi/*.py;tables/C_同风险对比.csv | 符合(曾修 1/3→1/n,敞口恒 1.000) |
| 11 | 指标定义分歧 | 16章 §16.5 / 09_performance_reporting;本库 回测/perf_audit/README §三 | 索提诺/卡玛两边定义不同却直接比大小;卡玛漏传 periods 被高估 | grep -n "sortino|calmar|periods=" 回测/perf_audit/crosscheck_quantstats.py | 符合(perf_audit 为仲裁基准) |
| 12 | 制度切片 / 事后窗口 | 16章 §16.6 / notebook 10_regime_backtest_analysis;01章 §1.4 | 子区间"恰好都赢"是用事后挑的窗口;把 regime 当择时信号 | grep -n "子区间|regime|窗口" 回测/engine_multi/*.py | 符合(regime 只作风险透镜) |
| 13 | 尾部风险与回撤路径(只报波动/夏普) | 19章 §19.4(drawdowns / path risk / time-to-recovery)/ notebook 01_var_cvar、02_exit_strategies;§19.6 / 06_stress_testing | 只报波动与夏普,不报 VaR/CVaR、回撤时长与恢复期;危机期不单独看 | grep -rn "VaR|CVaR|underwater|水下|恢复" 回测/*/**.py | 存疑(有 最大回撤,但无 VaR/CVaR/回撤时长系统产出) |
| 14 | 风控自适应泄漏 | 19章 §19.7 / notebook 10_ml4t_backtest_risk_demo、07_drift_detection | 波动目标/止损/仓位上限用了未来信息;自适应参数在 IS 上挑 | grep -n "vol_target|rolling(" 回测/engine_v1/*.py | 符合(vol_target 用 sigma60 = rolling(60).std(),无前视) |
| 15 | 似然退化 / 方差参数弱识别 → 边界解(v1.0,2026-10-02 新增) | 本库实测(回测/kalman/报告_卡尔曼嵌入评估_2026-10-02 §五/§七);ML4T §9.2 原话「the bound is the only thing standing between this filter and the identity function」 | 状态空间模型的 MLE 看起来收敛(success=True、无 traceback),但解落在优化边界上:所有重估窗的 logθ 100% 钉在下界、参数漂移 max/min = 1.000、std(标准化残差)≈0.18(理想 ≈1);放松边界后 nll 继续下降(−526.66 → −1356.81)= 似然无上界 | python3 回测/kalman/kalman_selftest.py --only 3(重估逐条日志);python3 回测/kalman/kalman_report.py 表 2/表 4;放松边界诊断 kf_bounds_diag.py | 不适用(本库 engine_v1/engine_multi/portfolio.py 均未使用状态空间/卡尔曼类滤波器 ⇒ §二 三处自查不受影响);对使用此类滤波器的新产出判 违背(不可部署) |
| 16 | 阈值闸 / 状态闸的工程校验(v1.0,2026-10-02 新增) | 本轮实测(回测/kalman/报告_卡尔曼嵌入评估_2026-10-02 §七·补);方法论判据 B | 任何"阈值触发"的闸门上线前必须验三件事:① 被比较的量的量纲与分位(是否无量纲、是否近似常量);② 触发率合理(不得 ≈0% 或 ≈80%——两个极端都说明阈值语义失效);③ 闸门输出真能进入决策链(迟滞/状态机是否会把它吞掉)。反例(本库实测):|kf_innovation| ≥ 3 在残差 std≈0.18 上触发 0/104 = 0%;kf_uncertainty > 扩张 90% 分位 在近似常量(折内 p0=p90=0.00423)上恒定触发 76%;两者置「无法评估」后又被引擎 l1_cand != "无法评估" 规则在 l1_confirm_days=2 的折里静默吞掉 | python3 回测/kalman/v2_gate_diag.py(逐折两条闸条数 + 量纲分位;输出留档 cache/v2_gate_diag.log) | 对已上线闸门不适用(本库 engine_v1 现有闸门为 L0 阈值/L1 迟滞/L2 门控,均为文档定义且量化门槛明确 ⇒ §二 三处自查不受影响);对新产出判:未过三验即 违背(闸门语义未证) |
二、本库三处产出 · 机械自查(2026-09-27 实测)
规则:发现"违背"不许在本次审核中修改被审对象,只登记;修复必须另立任务(唯一例外:
engine_multi年化口径由用户单独授权,见 §三/P3-③)。
① 回测/engine_v1/(单资产四因子择时引擎)
| # | 陷阱 | 判定 | 证据行 |
|---|---|---|---|
| 1 | 前视偏差 | 符合 | duckerry_backtest.py:12「只用过去信息(信号 t 日收盘生成,t+1 日生效),无前视偏差」;:747 主循环同声明 |
| 4 | 成本与滑点 | 符合 | README.md:52「成本 6bps 单边(1bp 佣金 + 5bps 滑点);核心底仓不产生换手成本,只对卫星换手计费」 |
| 5 | 样本外/稳定性 | 符合 | ls wf/ → fold_0..5.json + oos_equity.csv + oos_summary.json(滚动 walk-forward,参数只在训练段择优) |
| 7 | 年化口径 | 符合 | 用 bar/ppy;perf_audit 实测与 quantstats 相对差 0.08%(= round(4) 量化) |
| 11 | 指标定义 | 符合 | 索提诺/卡玛定义已由 perf_audit 定位并记录 |
| 13 | 尾部/路径风险 | 存疑 | 有 最大回撤_策略(duckerry_backtest.py:1042),但无 VaR/CVaR/回撤时长产出 |
| 14 | 风控自适应泄漏 | 符合 | sigma60 = gold_ret.rolling(60, min_periods=40).std()(duckerry_backtest.py:361),滚动窗口无前视 |
| 8 | 分散幻觉 | 不适用 | 单资产卫星(其"无 alpha"结论是 §8 的反面证据:等敞口对比三项全输) |
结论:engine_v1 全项符合(尾部指标列为存疑)。 其 README 已自述"跑输被动持有"。
② 回测/engine_multi/(多资产组合,阶段 1)
| # | 陷阱 | 判定 | 证据行 |
|---|---|---|---|
| 1 | 前视偏差 | 符合 | 设计原则 3「无前视:t 期收盘算权重 → t+1 生效;成本在调仓当刻计」 |
| 4 | 成本与滑点 | 符合 | tables/D_再平衡x成本矩阵.csv 存在(成本多点扫描) |
| 5 | 样本外/稳定性 | 存疑 | tables/B_子区间.csv / B2_子区间_对齐口径.csv 是子区间切片,非滚动 walk-forward;阶段 1 未做 OOS(如实登记) |
| 7 | 年化口径 | 违背 → 已修复(v5.3.2) | 原 multiasset_backtest.py:240 years = (idx[-1]-idx[0]).days/365.25(日历跨度)→ 与 engine_v1/quantstats 的 bar/ppy 差 0.36%;P3-③ 已统一为 bar/ppy(复用 infer_ppy),重跑 perf_audit 实测相对差 0.36% → 0.05% |
| 10 | 现金约束/隐性杠杆 | 符合 | 曾修「恒定配比 1/N」硬编码 1/3 → 1/n,现平均敞口恒 1.000;avg_exposure 写入表 |
| 8 | 分散幻觉 | 存疑 | tables/E_分段相关系数.csv 有腿间相关,但权益腿仅 NDX(单一国家、高度集中科技巨头)→ 备忘 §六已警告:赢的子区间恰是纳指大牛市 = beta 选择,非资产类别分散;换腿稳健性仅 2016 起(弱检验) |
| 2 | 幸存者偏差 | 符合(低风险) | 标的为指数/现货/ETF,无个股退市;池切换(WTI 2016 起等)显式记录 |
结论:engine_multi 1 处违背(年化口径,已修复(v5.3.2))+ 2 处存疑(无滚动 OOS;权益腿单一 = beta 选择)。
③ 回测/perf_audit/(绩效指标交叉验证,研究层只读审计)
| # | 陷阱 | 判定 | 证据行 |
|---|---|---|---|
| 11 | 指标定义分歧 | 符合 | crosscheck_quantstats.py:289 记录 quantstats 索提诺 dn = x[x<0].std() 定义差异;卡玛漏传 periods 已于 v1.1 修复 |
| 7 | 年化口径 | 符合(且为仲裁源) | README.md §四 已知口径;实测 quantstats.stats.cagr 用 years = returns.count()/periods = bar/ppy,与 engine_v1 同源 |
| 3 | 多重检验 | 不适用 | 该目录是审计工具,不是策略搜索 |
结论:perf_audit 全项符合,并作为本库「年化/卡玛/索提诺」口径的仲裁基准。
三、结论
- 第 15 条(2026-10-02 新增):对本项目新增的卡尔曼模块(回测/kalman/README)判 违背(不可部署)——
重估窗 100% 钉在事前边界、参数漂移 1.000、似然随放松边界继续下降;该模块未接入生产,结论「无改善 → 不引入」。 本条不改变 §二 三处产出的判定。
- 第 16 条(2026-10-02 新增):阈值闸工程校验(量纲分位 / 触发率不得 ≈0% 或 ≈80% / 输出须真能进入决策链)。
本轮实测反例:innovation 闸 0%、uncertainty 闸恒定 76%、confirm=2 时被迟滞规则吞掉;本轮未修(先解决第 15 条的退化,再谈闸门)。
- 三处产出共发现:1 处违背(
engine_multi年化用日历跨度)、3 处存疑(engine_multi无滚动 OOS;engine_multi权益腿单一 = beta 选择;engine_v1无 VaR/CVaR/回撤时长),其余全部符合。 - 违背项的修复不在本审核动作内(未改被审对象);
engine_multi口径统一由用户单独授权执行(v5.3.2 / P3-③),修复后其年化与 quantstats 相对差实测 0.36% → 0.05%(2026-09-27;残留 0.05% =len(eq)vsreturns.count()的 1 个观测之差)。 - 诚实口径:本清单是审核脚手架,不是"通过即安全"的证书。ML4T §16.1 原话——回测不是"策略有效"的证明,而是"它能否在现实假设下被证伪"的结构化尝试。
- 未完成 / 下一轮:[待办] 为
engine_multi补滚动 walk-forward(补 #5 存疑);[待办] 补长历史非权益分散腿数据后重做换腿稳健性(补 #8 存疑);[待办] 补 VaR/CVaR/回撤时长(补 #13 存疑);[候选] PyPortfolioOptrisk_models.py收缩估计评估(用户已列为下一轮)。
四、出处索引(本次只读材料 → 用到的位置)
| 材料 | 用到的清单条目 |
|---|---|
01_process_is_edge/README §1.4(regime 是风险透镜,非择时信号) | #8 #12 |
16_strategy_simulation/README §16.1(回测即证伪)/ §16.2(协议)/ §16.3(引擎对比)/ §16.5(指标)/ §16.6(经济价值)/ §16.7(回测过拟合) | 全表 |
16.../01_backtest_first_principles(signal timing、next-open fill、cash constraint) | #1 #9 #10 |
16.../07_engine_divergence_anatomy(逐字段分解引擎分歧) | #7 #9 |
16.../10_regime_backtest_analysis(事前可观测状态切片) | #12 |
16.../11_sharpe_ratio_inference(夏普抽样分布 / 置信区间) | #3 #5 |
16.../12_dsr_validation(Deflated Sharpe / 选择偏差) | #3 |
16.../14_cost_sensitivity(成本网格 / 盈亏平衡费) | #4 |
18_transaction_costs/README §18.2(成本分类)/ §18.3(微观结构制度)/ §18.4(基线成本模型)/ §18.5–18.6(执行算法 / Almgren-Chriss) | #4 #9 |
19_risk_management/README §19.3(VaR/CVaR)/ §19.4(回撤与路径风险)/ §19.6(压力测试)/ §19.7(无泄漏自适应风控)/ §19.8(kill switch) | #13 #14 |
17_portfolio_construction/README §17.3(分散/集中度)/ §17.4(简单基准不是稻草人) | #8 |
02_financial_data_universe/README §2.3 / §2.5(复权 / PIT / 幸存者) | #1 #2 #6 |
07_defining_the_learning_task/README §7.4(搜索记账与多重检验) | #3 |
09_model_based_features/04_kalman_filter.py §9.2(只读参考;原文自述边界是拦住滤波器退化为恒等映射的唯一屏障)+本库实测 | #15 |
本轮闸门机制诊断(回测/kalman/v2_gate_diag.py,2026-10-02)+方法论判据 B | #16 |