4. 数据窥探偏差:反复回测同一段数据带来的幻觉
数据窥探偏差,说白了就是「看答案做题」。
我刚开始做量化那会儿,犯过最蠢的错误就是:拿着同一段历史数据,反复调参数、反复跑回测。每次看到曲线变漂亮,心里就美滋滋。结果实盘一跑,直接给我上了一课。
为什么会这样?因为你已经「偷看」了未来的信息。你调参的过程,本质上是在拟合这段特定的历史噪音。不是策略牛,是你运气好,刚好撞上了这段数据的规律。
核心问题:你回测的次数越多,找到「假规律」的概率就越大。这不是策略有效,这是统计上的必然。
4.1 数据窥探的本质
想象一下这个场景:
你有一组随机生成的股价数据。理论上,任何策略在这上面都应该亏钱。但你反复回测了100次,每次微调参数。最终,你找到了一个「年化收益30%」的参数组合。
你信吗?
我不信。因为这是典型的过拟合。你只是把随机噪声当成了信号。
数据窥探偏差的数学本质,其实很简单:
- 单次回测:你是在检验一个假设
- 多次回测:你是在从多个假设中挑一个最好的
- 反复回测:你是在对同一段数据做「数据挖掘」
每次回测,你都在增加「偶然发现好结果」的概率。这就像买彩票——买得越多,中奖概率越大,但中奖不代表你聪明。
我的经验:我个人习惯是,每次回测前先问自己:「如果这个策略在样本外失效了,我能接受吗?」如果答案是否定的,说明我已经在窥探数据了。
4.2 一个真实的教训
我曾经帮一个朋友诊断他的策略。他兴奋地给我看回测曲线——夏普比率2.5,最大回撤只有5%。
我问他:「你回测了多少次?」
他愣了一下:「大概...200多次吧。」
我又问:「每次都在同一段数据上?」
他点头。
我直接告诉他:「这个策略大概率是废的。」
他不信。结果实盘跑了三个月,亏了15%。
嗯,这就是数据窥探的代价。你看到的「优秀回测」,只是你反复调整后找到的「最佳幻觉」。
4.3 如何诊断数据窥探
这里有几个实用的诊断方法:
| 诊断方法 | 具体操作 | 判断标准 |
|---|---|---|
| 回测次数统计 | 记录你在同一段数据上的回测次数 | 超过50次就要警惕 |
| 参数稳定性测试 | 微调参数,看收益是否剧烈波动 | 波动越大,过拟合风险越高 |
| 样本外测试 | 留出一段数据从未看过,直接跑策略 | 样本外收益明显低于样本内,说明有问题 |
| 随机数据测试 | 在随机生成的股价上跑策略 | 如果还能赚钱,那肯定是过拟合了 |
警告:我曾经见过有人把同一段数据回测了上千次,然后宣称找到了「圣杯」。别做这种人。数据窥探不是策略问题,是认知问题。
4.4 知识体系:数据窥探的核心逻辑
下面这张图,是我自己总结的数据窥探偏差的完整逻辑链。你看完就明白了:
你看,整个链条非常清晰。从同一段数据开始,反复回测,找到最佳参数——但这只是统计上的偶然性。最终导致策略过拟合,实盘一跑就露馅。
4.5 如何避免数据窥探
我总结了几个实用方法,都是我自己踩坑踩出来的:
- 设定回测次数上限:我给自己定的规矩是,同一段数据最多回测20次。超过这个数,不管结果多好,我都直接放弃。
- 使用样本外数据:把数据分成训练集和测试集。训练集上随便调,但测试集只能跑一次。跑完就封存,不再碰。
- 随机数据验证:生成几组随机股价数据,在上面跑你的策略。如果还能赚钱,说明你找到的不是规律,是幻觉。
- 记录每次回测:我习惯用表格记录每次回测的参数和结果。这样能清楚看到,自己是不是在「追逐最佳曲线」。
避坑指南:我曾经因为数据窥探,浪费了整整三个月的时间。那段时间我每天都在调参,每天都能看到「更好的」回测结果。直到实盘亏钱,我才意识到自己一直在骗自己。
4.6 一个简单的代码示例
下面这个代码,演示了数据窥探是如何产生「假策略」的:
import numpy as np
import pandas as pd
# 生成随机股价数据
np.random.seed(42)
prices = np.random.randn(1000).cumsum() + 100
# 反复回测寻找「最佳」参数
best_sharpe = -np.inf
best_params = None
for i in range(100): # 回测100次
# 随机生成参数
lookback = np.random.randint(5, 50)
threshold = np.random.uniform(0.5, 2.0)
# 计算信号
signals = np.zeros(len(prices))
for j in range(lookback, len(prices)):
if prices[j] > prices[j-lookback] * (1 + threshold/100):
signals[j] = 1
elif prices[j] < prices[j-lookback] * (1 - threshold/100):
signals[j] = -1
# 计算收益
returns = np.diff(prices) / prices[:-1]
strategy_returns = signals[:-1] * returns
sharpe = np.mean(strategy_returns) / np.std(strategy_returns) * np.sqrt(252)
if sharpe > best_sharpe:
best_sharpe = sharpe
best_params = (lookback, threshold)
print(f"最佳夏普比率: {best_sharpe:.2f}")
print(f"最佳参数: lookback={best_params[0]}, threshold={best_params[1]:.2f}")
你运行这个代码,大概率会得到一个「不错」的夏普比率。但这是假的。因为数据是随机的,任何策略都不该赚钱。你只是从100次回测中,挑了一个运气最好的。
我的建议:如果你发现自己的策略在随机数据上也能赚钱,别高兴太早。这不是策略牛,是数据窥探在作祟。赶紧停下来,重新审视你的回测流程。
4.7 总结
数据窥探偏差,说白了就是「看多了,就以为看到了真相」。
你反复回测同一段数据,本质上是在做数据挖掘。你找到的不是规律,是噪音。你看到的优秀回测,只是统计上的偶然性。
记住一句话:回测次数越多,幻觉越大。
我个人的经验是:每次回测前,先问自己——「如果这个策略在样本外失效了,我能接受吗?」如果答案是否定的,说明你已经陷入了数据窥探的陷阱。
别骗自己。市场不会因为你回测做得好,就给你发钱。
公众号:蓝海资料掘金营,微信deep3321