数据窥探:同一段历史被你试了一百遍,它迟早会给你一个好看的答案
你把一套规则放到历史数据上跑了一遍——这个动作叫回测,就是「假装回到过去,按这套规则走一遍,看看会赚会亏」(术语卡在 回测)。第一次跑,结果很难看。
于是你把均线从 20 天改成 30 天,再跑。好一点了。再改成 25 天,又好一点。加个成交量的条件试试?嗯,这次曲线漂亮多了。止损从 5% 收到 3%,收益又上去了一截。前后折腾了大半天,你终于看到一条像样的资金曲线,心里那句话是:「找到了。」
这里有一个几乎没人会停下来问的问题——你刚才一共试了多少遍?
不是「你最后选中的那一版有多好」,而是「为了选出这一版,你淘汰掉了多少版」。这个数字,才是决定你手上那条漂亮曲线值不值钱的东西。而它,通常没人记录。
先说人话:它到底是件什么事
找一百个人来,每人扔十次硬币。按概率,大概率会有那么一两个人扔出了连续十次正面。
现在,把其余九十几个人请出房间,只把这一两位留下,给他们拍照,配上文字:「他们拥有惊人的硬币直觉。」
你看着照片会觉得荒唐,因为你知道还有九十几个人被藏起来了。可如果你不知道呢?如果有人只把这张照片递给你,说「看,连中十次,这不可能是运气」——你大概会认真考虑一下他是不是真有点门道。
同一件事换个说法:你举着手机连拍两百张,从里面挑出最好看的那一张发出去。 那张照片是真的,没有 P 过,快门响的那一刻你确实长那样。但它代表你平时的样子吗?完全不代表。它代表的是「两百次里最巧的那一瞬」。而看照片的人——包括三天后的你自己——都不知道分母是两百。
数据窥探说的就是这件事:同一份历史数据被反复试探,试的次数越多,找到「好看但无效」规律的概率越高。 注意这句话里没有任何一方在作弊。每一次试探都是诚实的,每一次回测的代码都没错,数据也是真的。问题只出在最后那一步——你只留下了最好的那个结果,却把「我试了多少次」这个分母扔了。
这也是它和别的偏差不一样的地方。前视偏差 那类问题是「你的程序错了」,改对就行;数据窥探里,每一步都是对的,错的是整个流程的形状。所以它没法靠 debug 解决,也没有任何一行代码可以被标红。
它在你的投资里怎么发生
你以为自己在研究,其实在试错
前面那个「20 天改 30 天再改 25 天」的场景,是最露骨的一种。但它不是最常见的。真正常见的是下面这些——它们看起来完全不像调参数:
换标的再试一遍。 这套方法在这只票上不行?换一只看看。换到第七只的时候成了,于是你说「这套方法适合这一类股票」。可你没换的那六只,就是被请出房间的那六个扔硬币的人。
换时间段再看一眼。 「过去五年不太行,但过去三年很好」——听起来像是发现了「近年来的市场特征」,实际上你只是在同一段历史里挪动了一个取景框,挪到画面好看为止。
换个指标试试。 MACD 不行换 KDJ,KDJ 不行换 RSI,加个布林带过滤一下。每换一次,就是一次试探,就是分母加一。
换个衡量标准。 这一条最隐蔽。收益率不好看,那看夏普比率(一个「每承担一份波动能换回多少收益」的比值,术语卡在 夏普比率);夏普也不好看,那看最大回撤;回撤也一般,那看胜率。换一把尺子重新量,本质上和换一个参数重新跑,是同一个动作。
只保留赚钱的那一部分。 freqtrade 的工具文档里有一句警告,位置在「导出历史检验结果、生成一份交易标的清单」这个功能旁边——它提醒你,只挑那些赚了钱的标的组成清单,会得到一个过度贴合历史的策略,在未来的数据上不会好用。这句提醒放在那里很有意思:那个功能本身就是「一键把输家从名单里删掉」,而删掉输家,正是把分母藏起来的最快方式。
你继承了别人的窥探,而且完全不知情
这一层比自己调参数严重得多。
你在网上看到一套策略,附了一张漂亮的回测图。你不知道的是:作者为了得到这张图,试了多少个版本?更要命的是——那些试出来不好看的作者,压根不会发帖。你在公开渠道能看到的每一套策略、每一张回测图、每一个「历史年化 XX%」的截图,都是幸存者。 分母是整个互联网上所有做过尝试的人,而它不可见。
这和 幸存者偏差 是亲戚,但不是一回事:幸存者偏差讲的是「样本里只剩下活下来的公司/基金」,数据窥探讲的是「结论里只剩下试成功的那次尝试」。前者是数据集的问题,后者是流程的问题。
更深一层:同一份市场历史,几十年来被全世界的研究者、机构、教科书反复筛过。一个「在历史数据上显著有效」的规律,可能在你第一次听说它之前,就已经被几千人试过几万遍了。你拿到的不是一个未经检验的假设,而是一个已经从巨大分母里被筛出来的赢家——它是真规律,还是那个连中十次的人?光看它自己的成绩单,你分不出来。
关键在于:分母是不可见的
停一下,把这条单独拎出来,因为它是整篇文章的核心。
你手上永远只有分子——那个最终被你选中的、成绩最好的版本。分母(一共试了多少次)不在任何文件里。它不会被记录,因为失败的尝试没人保存;它不会被报告,因为没人觉得那算「结果」;它甚至不在你的记忆里,因为你调参数的时候注意力全在「怎么让它变好」上,没人一边调一边计数。
而一个只有分子、没有分母的成绩,什么都说明不了。「连中十次」这个成绩本身是没有信息量的,有信息量的是「一百个人里连中十次」还是「一个人第一次就连中十次」。后者才叫本事。
系统层:四种不同的应对姿势
这才是真正值得慢慢看的部分。数据窥探不是一个 bug,而是一种流程病,所以对付它的方式也不在代码的某一行里,而在整个流程的形状上。把几种做法摆在一起读,你才会明白它到底是什么。
| 视角 | 做法 | 一句话说清 |
|---|---|---|
| freqtrade | 参数自动寻优(hyperopt) | 把窥探工业化:把「试一百遍」变成一条命令 |
| Vibe-Trading | 随机对照 + 抬高门槛 | 给分母定价:造一个假信号当尺子,抬高判定线 |
| qlib | 多随机种子的均值 ± 波动 | 展示不确定性:让「名次差距」和「随机噪音」同框对比 |
| 你的日常 | ——(没有解法) | 一边试一边忘,分母永远是零 |
姿势一 · 把窥探工业化:一条命令试一千遍
freqtrade 有一个叫 hyperopt 的模块,做的事情就是自动帮你调参数。你把想调的东西(进场条件、出场条件、止损、止盈档位)标记成可优化的,它就一遍一遍地跑历史检验,每跑完一遍把结果丢给一个「目标函数」打分,再根据分数决定下一组参数往哪个方向试。
执行的时候有一个参数决定试多少轮:
freqtrade hyperopt --config config.json --hyperopt-loss <lossname> --strategy <name> -e 500 --spaces all
-e 500 就是试五百轮。文档里提到,经验上跑到几百上千轮之后成绩通常不会再明显变好(这属于经验描述,不同策略差别很大)。
请把这件事在脑子里过一遍:五百轮,意味着这份历史数据被翻来覆去看了五百遍,最后挑出最好看的那一轮。 前面那个「一百个人扔硬币」的比喻里,这相当于叫来五百个人。而这条命令跑完,报告只会给你看第一名。
这不是在说这个工具不好——恰恰相反,把试错自动化、可复现、留下记录,比你手动瞎改一下午强得多。真正值钱的,是它的文档对副作用交代得非常诚实。有三处特别值得抄下来:
第一处:多跑几次,随机起点不同,最优解就不一样。 文档里明确写了,用不同的随机起点跑上几千轮,很可能得到不同的结果。这句话的分量需要品一下——如果「最优参数」是市场的真实结构,那么换个搜索起点应该找到同一个地方;它会变,说明你找到的那个点很大程度上是搜索路径的产物,不是市场的产物。
工具还提供了固定随机起点的开关,用来让结果可复现。可复现是工程上的好事,但它复现的是「同一次运气」,不是「同一个真相」。
第二处:搜索精度被故意限制。 文档在几个默认搜索空间下都重复了同一句提醒:数值被限制到小数点后三位,因为比这更精细的取值通常会产生过度贴合历史的结果。
这句话把机制点破了:精度越高 → 可试的组合越多 → 分母越大 → 挑出来的第一名越可能是噪音。 「限制精度」听上去像是为了省算力,其实是在主动缩小分母。这是很硬核的一条设计取向——不是让你搜得更准,是不让你搜得太细。
第三处:搜索空间会被穷尽。 文档里描述过这样一条提示:
The objective has been evaluated at this point before.
意思是这组参数之前已经试过了。文档解释说,这说明搜索空间基本被试遍了,或者陷进了局部最优。举的例子很朴素:一个只有十来种取值的参数,跑不了几轮就没有新东西可试了。
把这条提示翻译成人话就是:「你已经把这份数据的每一个角落都看过一遍了。」 到这一步,你选出来的第一名,一定是这份历史里最贴合的那个——但「最贴合历史」和「最能应付未来」,从来不是同一件事。
顺便说一句,可选的目标函数(那把用来打分的尺子)本身就有十几种:只看利润的、看夏普的、看索提诺的、看最大回撤的、看多个指标平衡的……每一种都是一个合理的选择。但如果你用第一种跑完不满意,换第二种再跑一遍——那你又给分母乘了一次。
姿势二 · 给分母定价:造一个「没有信息的假信号」当尺子
Vibe-Trading 里有一个专门为这件事写的模块,思路和上面完全不同,值得细看。
它要解决的问题是:怎么判断一个因子(可以理解成「一套给股票打分的规则」)是真有效,还是碰巧?
常规做法是看它的 IC——IC 就是「这套规则给出的打分」和「后来实际涨跌」之间的相关程度,越接近 0 越像瞎猜——再算一个 t 值,也就是「这个成绩离纯属巧合有多远」的一把尺子,数越大越不像巧合。IC 够高、t 值够大,就判定为有效。
这个模块的文件开头直接指出了这套常规做法的毛病:它是拿成绩去和「零」比。也就是问「这个因子比完全没用强吗」。而这个问题太容易通过了——一个只是蹭到了大盘涨跌、或者只是买了小盘股的因子,也能轻松打败「零」。
它的替代方案很漂亮:不和零比,和一个假的自己比。
具体做法是把这个因子的打分表按行打乱——每一天的那一排分数还是原来那些数,只是被随机分配给了不同的股票。这样造出来的东西,注释里的说法是:一个没有任何信息、却拥有和原版一模一样统计外形的因子。用它当基准线,才是公平的对照。
如果你的因子只是比零强,而不比这个「假的自己」强,那它的成绩就是这份数据的形状给的,不是它自己挣的。
在这个基础上,它给每个因子发的不是「有效/无效」两档,而是四档:
StrictCategory = Literal[
"confirmed_alive", # signal beats random in full sample AND in OOS (when provided)
"train_only", # signal beats random in train, fails in test
"reversed_strict", # signal IC < random IC with negative alpha_t < -2
"noise", # alpha_t in [-2, 2]; indistinguishable from random
]
翻成人话:全样本和样本外都打赢随机对照的,才算「确认存活」;只在训练段赢、到了测试段就趴下的,单独标成「只在训练段成立」;成绩明显反过来的算「反向」;剩下那些和随机对照分不出高下的,直接叫噪音。(那几个 ±2 是代码里的默认门槛值,可配置。)
设置「只在训练段成立」这一档,是这套分类里最见功力的地方。它没有把这类因子简单地扔掉或留下,而是给它一个专门的名字——因为这一档正是数据窥探最典型的产物:在你反复试探过的那段数据上它成立,换一段就不成立。
还有一条更硬的:这个模块的门槛值是可以调的,注释里援引了 Harvey-Liu-Zhu 2016 年那篇关于「多重检验与预期收益横截面」的研究,说的是——当你在一整个因子库上做筛选时,判定门槛必须明显抬高(注释里记的是从约 2 提到约 3.5,具体以你读的那篇文献为准)。
这是对付数据窥探最正统的思路:试的次数变多,及格线就得跟着往上抬。 试一次就通过的分数,试四百次的时候不该算通过。这条原则在另一份统计笔记里写得更直白:测一百个因子、按常规显著性标准筛选,光靠运气就会有几个「显著」;所以要用多重比较修正,并且不许一直调到通过为止,测试方案得事先定好。
模块注释里还记了一次实证审计的结论:在一项针对 A 股单因子的审查中,每一个因子都能在某一组参数设置下通过原始的 IC 检验,但扛住随机对照的只是其中很小一部分。「每一个都能在某组参数下通过」——这一句就是数据窥探的完整定义。
姿势三 · 把不确定性摆到台面上
qlib 的仓库里有一份模型性能对照表,二十几个预测模型,全都跑在同一份数据集、同一段测试期上,列出信息系数、年化收益、最大回撤等等。
这张表本身,就是一次教科书级的集体窥探现场:几十个方法,同一份数据,排出名次。第一名有多少是真本事,有多少是这份数据恰好偏爱它的结构?
有意思的是这份文档处理这个问题的方式。它没有藏,而是把每个数字都写成「均值 ± 波动」,并注明这是用不同随机种子跑二十次算出来的。表里的行长这样(取自快照中的示例行,数值会随仓库更新变化,这里只看结构):
| Linear | Alpha158 | ... | 0.0692±0.00 | 0.9209±0.00 |
| TRA(Hengxu Lin, et al.) | Alpha158 | ... | 0.0718±0.02 | 1.0835±0.35 |
请对比两个东西:两行之间的差距,和后一行自己的那个 ± 号后面的数。 一个复杂模型比一个简单线性基准高出来的那一点,远远小于它自己在二十次随机种子之间的摆动幅度。换句话说,如果你只跑一次,运气好的一次和运气差的一次,能把这个「名次」完全颠倒过来。
那个 ± 号,就是这张表最诚实的部分。它等于在告诉你:别把名次当结论。 文档里还有一段很坦率的说明,大意是团队投入的调优资源有限,某些模型的潜力可能超出表里的表现——这句话进一步提醒你,一张排行榜衡量的从来不只是方法本身,还包括谁在上面花了多少工夫调。
顺带一提,同一个思路在 freqtrade 的机器学习模块里也有:那里有一个给训练数据主动加随机扰动的选项,说明写的是为了防止过度贴合。往数据里掺噪音听起来反直觉,逻辑却是一致的——如果一个规律经不起一点点扰动,那它本来就不是规律。
姿势四 · 你的日常:分母永远是零
把上面三种摆完,再看这一行,大概会有点不舒服。
你这一年里换过多少个选股思路?每个思路你观察了多久,觉得不行就换掉了?那些被你换掉的,有记录吗?
没有。你只记得现在正在用的这一套,以及它「回看起来挺有道理」。你脑子里那份成绩单永远只有分子,因为分母在你放弃某个想法的那一刻就被删掉了——而且删得毫无痛感,你甚至不觉得那算一次「尝试」。
更麻烦的是,人在这件事上有一套自动化的事后配音。找到一个好看的结果之后,大脑会立刻补上「为什么它有道理」的解释:这个参数对应的是主力建仓周期、那个条件过滤掉了假突破。解释一旦成立,你就再也想不起来这个参数是从八十个候选里挑出来的了。
这一点和 大数定律与赌徒谬误 讲的是同一族的认知毛病:人天生擅长在随机里找规律,而且找到之后会立刻相信。区别在于,赌徒谬误让你亏在一次下注上,数据窥探让你亏在一整套自以为经过验证的方法上。
为什么它几乎无法自查
这是本篇最想让你记住的部分。
分母不可数,所以严格的修正做不到。 理论上有一整套多重检验的修正方法,思路都是「测得越多,门槛越高」。但它们要求你知道自己一共测了多少次。而真实的研究过程里,这个数字包含:你手动改过的每一版、你换过的每一个标的、你调整过的每一个时间窗、你换过的每一把衡量尺子,还有你在别处读到过因而带着先验去试的那些方向。这个数没人算得出来。 所以现实里的做法只能是近似——用随机对照当替身,或者干脆把门槛抬得远高于常规。
它是唯一一个「越努力越危险」的偏差。 别的问题你多花时间就能改善:数据洗得更干净、成本假设更贴近现实、代码检查更仔细。数据窥探反过来——你在同一份数据上花的时间越多、试的方案越多、越是不甘心地「再试一个」,你最终选中那个漂亮结果里的运气成分就越大。勤奋在这里是负资产。 这一条违反所有人的直觉,所以几乎没人真的按它行事。
它不留痕迹。 一份最终的策略文件,看不出它是第一次写成的,还是第三百次改成的。同样一段代码、同样一条曲线、同样一份报告,背后可能是一次运气,也可能是三百次筛选。结果本身不携带自己的来历。 你没法通过检查结果来判断它经历过什么。
别人的窥探你继承了,还没法追溯。 你从书上、帖子里、卖方研报里拿到的每一个「历史上有效」的规律,都已经经过了不知道多少轮筛选。你拿它去检验一遍,通过了,于是你以为自己独立验证了它——但你验证用的,很可能就是当初把它筛出来的那份数据。这不叫验证,这叫复读。
做出来的东西还真的能用一阵子。 这一条最坑。一个由数据窥探产出的方法,未必立刻失效。它可能在接下来几个月里恰好还行,让你更加确信,从而加大投入;等它真的不行的时候,你的仓位已经不是当初那个规模了。
「我又拿另一段数据验证了一遍,也成立」——这句话通常不成立。 如果那段「另外的数据」你之前已经翻看过、或者用它来决定过要不要保留某个版本,那它就已经不是干净的了。样本外数据是一次性用品:看第一眼是检验,看第二眼开始,它就变成了你调参数的对象。真正的样本外只有一份,而且用一次就没了。这一层的机制,样本内与样本外 那篇讲得更细。
失效边界与常见误用
把它和过拟合当成一回事。 两者高度相关但不是同义词。过拟合说的是「一个模型的参数被历史噪音磨得太合身」,是单个模型内部的毛病;数据窥探说的是「在多个候选里挑出最好看那个」,是选择流程的毛病。哪怕每个候选模型都简单朴素、单独看毫无过拟合迹象,只要你从五百个里挑第一名,数据窥探照样发生。分界见 过拟合:把噪音当成规律 和术语卡 过拟合。
以为「不调参数」就免疫。 你不调参数,但你换标的、换周期、换指标、换尺子。这些全都是试探。真正的免疫只有一种:先把要检验什么、用什么标准判定、失败了怎么办全部写下来,然后只跑一次——而这是几乎没人做得到的自律。
把结论推到「所以研究没用」。 不是。数据窥探不是禁止你搜索,是要求你给搜索的规模定价。同样一个成绩,试了三次得到和试了三百次得到,值的钱差好几个数量级。该改变的不是「要不要试」,是「试完之后你有多信」。
只怀疑别人的回测,不怀疑自己的。 看到网上的漂亮曲线,你的第一反应是「肯定调出来的」;看到自己电脑上的漂亮曲线,第一反应是「我找到了」。这两条曲线的生成流程,通常一模一样。
忘了这件事和市场有效性是连着的。 一个方法如果真的简单有效、又能被普通人在公开数据上试出来,那它多半早就被试过了。有效市场假说 EMH 那篇讲的正是这个逻辑链条:你能想到的,别人也能想到,而且他们已经试过了。历史数据这口井,被打过的次数远超你的想象。
小结
- 数据窥探 = 同一份历史被反复试探,只保留最好看的那次结果,同时丢掉「一共试了多少次」这个分母。每一步都没错,错的是整个流程的形状。
- 试探不只是调参数:换标的、换时间段、换指标、换衡量尺子、只保留赚钱的品种,全都算一次,分母都要加一。
- 你继承的窥探比你自己做的更多:公开渠道能看到的每一张漂亮回测图,都是幸存者;每一个「历史有效」的规律,都已经被无数人筛过。
- 系统层的三种应对——把试错自动化并诚实标注副作用(换个搜索起点最优解就变,说明它是搜索路径的产物)、造一个「没有信息但外形相同」的假信号当对照并按搜索规模抬高门槛、把多次随机种子的波动摆出来让你看清名次差距是不是被噪音淹没。
- 它是唯一一个越勤奋越危险的偏差,而且做出来的东西还真能好用一阵子——等它失效时,你的仓位往往已经不小了。
一句话记住这篇:试一百次挑出来的最好成绩,不叫本事,叫运气;除非你把「试了一百次」这件事也写进成绩单里。
本文所引源码与文档均来自上述快照版本,是阅读代码与文档得出的机制说明,不是运行结果;具体行为请以你手上那个版本为准。回 量化与 AI 投研概念库 看其它概念各自在说什么,或从 量化与 AI 投研卷 顺着读;本站内容为投资教育,不构成任何投资建议,边界见 免责声明。
本文为投资教育整理,关键数据与结论请结合下列权威来源验证。
- freqtrade 源码快照 b3404c9(2026-08-18):docs/hyperopt.md、docs/utils.md、docs/freqai-parameter-table.md ↗
- Vibe-Trading 源码快照 3a752d5(2026-08-04):agent/src/factors/bench_runner_strict.py、agent/src/skills/quant-statistics/SKILL.md、agent/src/skills/correlation-analysis/SKILL.md ↗
- qlib 源码快照 79633dd(2026-07-23):examples/benchmarks/README.md ↗