回测可信度审查:样本外测试的五种常见自欺

回测可信度审查:样本外测试的五种常见自欺

本文是《Python 量化回测系统工程》系列的第 2 篇。上一篇讲了滑点和手续费必须默认开启,这一篇讲一个更隐蔽的问题:你辛辛苦苦做的"样本外测试",可能根本不算样本外。

一、先说一个反直觉的事实

很多人的验证流程是这样的:拿一段历史数据调参数,调到曲线好看,然后换一段没用过的数据跑一遍------跑赢了,就宣布"通过样本外验证"。

问题是:这一步几乎证明不了任何事。

原因很简单。你在调参时看过全样本的分布特征、看过最大回撤出现在哪、看过哪段行情最难熬。这些信息已经进了你的脑子,也进了你最终选定的那组参数。之后再切一段数据出来跑,它和"样本外"这三个字要求的独立性,差得很远。

真正可信的样本外,成本比大多数人愿意付的高得多。下面五种是最常见的自欺方式,按隐蔽程度排序。

二、五种自欺

1. 参数是在全样本上挑的,样本外只是"复述"

症状:先跑全区间,看哪组参数最好,然后宣布"这组参数在样本外也表现不错"。

为什么是自欺 :参数的挑选过程已经用到了样本外区间的信息。这叫窥探偏差(look-ahead in selection),它不像未来函数那样显眼,所以更难被发现。

工程上的解法 :参数选择必须在训练段内完成,且训练段要能真正封死------写进配置、落盘、带时间戳。选完之后,样本外区间只能跑一次,结果直接进报告,不允许回头改参数。

2. 跑输了就换一组参数再跑一次

症状:样本外跑输了,"这组参数可能过拟合了",换一组再试,直到跑赢为止。

为什么是自欺 :这是多重检验问题。你不是在验证一个假设,你是在用随机性刷显著性。跑 20 次,哪怕策略毫无预测能力,也总有一两次看起来漂亮。

工程上的解法 :样本外只允许跑一次 。次数写进报告头部。如果确实需要迭代,就换一套全新的、从未参与过任何调参的数据,并且把前面所有次的尝试都记进台账------不是藏起来,是摊开。

3. 样本外区间太短,样本量根本不够

症状:样本外只有两三个月、几十笔交易,然后拿"年化收益"当结论。

为什么是自欺:几十笔交易的波动区间宽到能吞掉任何结论。你算出来的"年化 15%",置信区间可能是 -20% 到 +50%------它不叫结论,叫噪声。

工程上的解法 :给出样本量下限,达不到就明确标注"样本不足,结论仅供参考"。同时分段统计:把样本外再切成若干子段,看结论在多少个子段上成立。全区间一个数字很漂亮、子段上一半在亏,这本身就是过拟合的信号。

4. 切法让两段数据"长得一样"

症状:随机打散数据切训练/测试集。

为什么是自欺 :时间序列不能随机切。随机切会把相邻 K 线分到两边,两段数据的市场状态高度雷同,测试集等于训练集的重影。这是把时序问题当成截面问题处理,也是从机器学习照搬方法论时最容易犯的错。

工程上的解法 :严格按时间切,且留出缓冲带(训练段末尾和测试段开头之间空出一段不用),避免相邻bar的信息渗透。更严的做法是 walk-forward:滚动推进,每个窗口重新训练、只测下一段。

5. 只看收益,不看"结论有多脆"

症状:样本外跑赢了就收工,从不问"参数稍微动一下会怎样"。

为什么是自欺:如果最优参数周围一圈全是坑------参数从 20 挪到 21 收益直接腰斩------那你找到的不是规律,是噪声里的一个尖峰。实盘里任何微小的市场变化都会把你从尖峰上推下去。

工程上的解法 :跑参数邻域敏感性网格。把主参数在其最优值附近做网格展开,看收益曲面是平缓的高地还是孤立的针尖。高地才能用,针尖必须弃。

三、把这五条变成一份可执行的验证协议

我们把它固化成了三段式,每次交付前必跑:

阶段 数据 允许的操作 不允许的操作
样本内 训练段 调参、选结构、剪枝 ---
样本外 紧邻的下一段,含缓冲带 只跑一次,结果直接进报告 回看后改参数、换参数重跑
跨品种 其它品种同期数据 跑一次,看结论是否迁移 针对该品种重新调参

配套三条硬要求:

  1. 参数邻域网格:主参数至少做 3×3 展开,收益曲面必须是高地不是针尖;
  2. 分段统计:样本外至少再切 3 个子段,结论在多数子段上成立才算通过;
  3. 滑点敏感性:和上一篇一样,0 / 1 / 2 跳三档都要跑,三档结论方向必须一致。

四、用一组真实数字看什么叫"结论很脆"

下面是同一套引擎、同一份作业,唯一区别是滑点档位的三次运行结果(资金 100 万,日线,含手续费):

滑点档位 累计收益 最大回撤
0 跳 +6.24% 0.49%
1 跳 +2.72% 1.08%
2 跳 +2.49% 1.16%

收益从 +6.24% 掉到 +2.49%,掉了六成;最大回撤反而从 0.49% 扩大到 1.16%。同一个"策略",在不同摩擦假设下是两回事。

另外几个同批次的数字:期末权益 1,024,907.77,胜率 60%,盈亏比 3.09,滑点审计出来的平均滑点是 +3.00 跳。

注意最后那个数------审计出来的实际滑点(3.00 跳)比我们测试时假设的最严档位(2 跳)还要大 。这就是为什么报告里必须同时给两个数:回测数,和一个打了折的保守参考数。我们的经验系数是 ×0.55(样本外 0.7 × 实盘 0.8),两个数分两栏放,读者自己判断。

五、三条给工程师的建议

  • 验证的成本要花在"让数据变干净"上,不是"让曲线变好看"上。一次诚实的失败验证,比十次漂亮的可疑验证值钱;
  • 把"我跑过几次"写进报告。次数是关键元数据,藏起来就是自欺的开始;
  • 结论的脆性比结论本身重要。能说清"这个结论在什么条件下会失效",才算真懂了这个结论。

下一篇预告

《先算这一笔:用一个公式判断你的收益目标做不做得到》------讲「所需 Calmar = 年化目标 ÷ 回撤容忍度」,以及为什么在动手写第一行回测代码之前,就该先把这个数算出来。


本文数据来自一套真实的 Python 回测作业(演示模式),所有数字可复现。系列文章只讲工程与数据口径,不涉及任何具体策略、指标与参数。

相关推荐
zyseo81 小时前
谷歌SEO 网站迁移 301 映射脚本:旧站 URL 自动匹配新站结构
开发语言·python
伞伞悦读1 小时前
【第59期】转置不是摆好看:Python 矩阵乘法、逆矩阵与线性方程组
python
Dmz丶1 小时前
前端转 AI 100 天 Day 29:Nginx 反代 + HTTPS——让服务“能上公网“
python
麦序子1 小时前
用 Python + python-docx 批量生成带表格样式的装修报价单 Word 文档(含合计公式)
开发语言·python·自然语言处理·word·python3.11
1-1=01 小时前
Python PyCharm
开发语言·python·pycharm
梦影_1 小时前
Langchain简单快速上手教程(九)—— 核心组件之少样本提示
人工智能·python·自然语言处理·中间件·langchain
机械设计扫地僧1 小时前
用PYTHON工具对FAG INA CS 系列薄壁轴承进行初步选型筛选
python
老孟说禅2 小时前
自动化测试入门(一)
开发语言·python
miofly2 小时前
GitHub 今日推荐|Snapintosh:用 ARM EL2 监控器骗过内核,让骁龙芯片笔记本跑 macOS
python·开源·github