第三节:样本内与样本外——如何科学划分训练集、验证集、测试集,避免未来信息泄露
做量化策略的朋友,十有八九都经历过这种场景:回测曲线漂亮得像教科书,一上实盘就崩。嗯,问题大概率出在数据划分上。
我刚开始做策略研发那会儿,也犯过这种错。把整个历史数据丢进去训练,然后拿同一批数据做回测,结果跑出来年化收益50%,回撤不到5%。当时我还以为自己发现了圣杯,结果实盘一个月就亏了20%。
说白了,这就是典型的「未来信息泄露」。你让模型看到了它本不该看到的数据。
为什么数据划分这么重要?
量化策略的本质,是用历史规律去预测未来。但这里有个坑:历史数据里包含的信息,有些是「可重复的规律」,有些只是「偶然的噪声」。
如果你不把数据分开,模型就会把噪声也学进去。结果就是——它在历史数据上表现完美,但一到新数据就失灵。
我个人习惯把数据划分比作「考试」:
- 训练集 = 课本,模型用来学习知识
- 验证集 = 模拟考,用来调参和选模型
- 测试集 = 高考,只有最后才能看成绩
你想想看,如果一个人拿着高考答案去复习,考满分有意义吗?
三种常见的划分方式
不同的策略类型,适合不同的划分方法。我整理了一下,主要有三种:
| 划分方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 固定时间划分 | 日频、周频策略 | 简单直观,容易实现 | 可能错过市场风格切换 |
| 滚动时间窗口 | 高频策略、趋势跟踪 | 适应市场变化 | 计算量大,容易过拟合 |
| 随机划分 | 截面选股、因子测试 | 消除时间依赖 | 可能引入未来信息 |
我在项目中遇到过最坑的事,就是有人用随机划分做时间序列策略。结果训练集里混进了未来的数据,模型「提前知道」了明天的涨跌。这种错误特别隐蔽,新手很容易踩坑。
固定时间划分:最稳妥的做法
对于大多数量化策略,我建议用固定时间划分。具体比例可以参考:
- 训练集:60% - 70%
- 验证集:15% - 20%
- 测试集:15% - 20%
举个例子,假设你有2010年到2023年的数据:
# 伪代码示例
data = load_data('2010-01-01', '2023-12-31')
train = data['2010-01-01':'2018-12-31'] # 训练集
valid = data['2019-01-01':'2021-06-30'] # 验证集
test = data['2021-07-01':'2023-12-31'] # 测试集
这里有个细节要注意:验证集和测试集之间,最好留一段「缓冲期」。比如验证集到2021年6月,测试集从2021年7月开始。这样可以避免数据泄露。
滚动时间窗口:应对市场风格切换
固定划分有个问题:如果市场风格发生了剧烈变化,模型可能就失效了。比如2015年的A股和2018年的A股,完全是两个世界。
这时候,滚动时间窗口就派上用场了。
具体做法是:
- 设定一个固定长度的窗口(比如3年)
- 每次训练用窗口内的数据
- 预测窗口外下一段时间的数据
- 窗口不断向前滚动
我记得有一次做CTA策略,市场从趋势行情突然变成震荡行情。固定划分的模型直接崩了,但滚动窗口的模型还能勉强撑住。虽然收益下降了,但至少没亏钱。
# 滚动窗口示例
window_size = 3 * 252 # 3年交易日
step_size = 63 # 每季度滚动一次
for start in range(0, len(data) - window_size, step_size):
train = data[start:start + window_size]
test = data[start + window_size:start + window_size + step_size]
# 训练模型,预测测试集
避免未来信息泄露的五个要点
这部分是我最想强调的。很多看起来不起眼的操作,都会导致未来信息泄露。
🔑 五个关键要点
- 特征计算只能用历史数据——比如计算20日均线,只能用当天之前的数据
- 标准化/归一化要在训练集上做——然后用训练集的参数去处理验证集和测试集
- 因子筛选不能看测试集——选因子的时候,只能用训练集和验证集
- 回测和训练要严格分离——不要在回测过程中反复调整模型
- 警惕「幸存者偏差」——退市的股票也要包含在数据里
我曾经犯过一个低级错误:在计算因子的时候,用了整个数据集的最大值和最小值来做归一化。结果测试集的信息「提前」流入了训练集。后来发现这个问题的时候,我恨不得抽自己一巴掌。
验证集的使用技巧
很多人把验证集和测试集混为一谈,其实它们的作用完全不同。
验证集是用来做模型选择的。你训练了10个不同的模型,用验证集挑出最好的那个。测试集则是用来最终评估的。
我个人习惯的做法是:
- 先用训练集训练多个模型
- 在验证集上比较它们的表现
- 选出最好的模型
- 最后在测试集上做一次评估
如果测试集的结果和验证集差距很大,说明模型可能过拟合了。这时候不要急着调整模型,而是应该回去检查数据划分有没有问题。
实战中的常见陷阱
说了这么多理论,来点实际的。我总结了几种最常见的坑:
- 陷阱一:用未来数据计算技术指标。比如用当天的收盘价计算当天的均线——这在实盘中根本做不到
- 陷阱二:在训练集和测试集之间来回切换。今天用训练集A、测试集B,明天换成训练集B、测试集A
- 陷阱三:数据划分后不做检查。有时候数据本身就有时间戳错误,导致划分失效
- 陷阱四:忽略数据中的「断点」。比如股票停牌、数据缺失,这些都会影响划分
嗯,这些坑我基本都踩过。每次踩完都告诉自己:下次一定注意。但说实话,做策略研发的时候,太容易陷入「追求完美回测」的执念里了。
一个完整的划分流程
最后,我给大家整理了一个标准流程。每次做新策略的时候,按这个流程走一遍,基本不会出大问题:
- 加载原始数据,检查时间戳完整性
- 按时间顺序排序,确保没有乱序
- 划分训练集(60%)、验证集(20%)、测试集(20%)
- 在训练集上计算特征和标签
- 用训练集的参数标准化验证集和测试集
- 训练模型,在验证集上调参
- 最终在测试集上评估一次
- 记录结果,封存测试集
这个流程看起来简单,但真正做到位的人不多。我见过太多人,在第三步就偷懒了——随便切一刀就开始训练。结果嘛,可想而知。
数据划分这件事,说白了就是「管住自己的手」。不要因为回测结果好看就忍不住去调整,不要因为测试集表现差就去修改模型。保持纪律性,才是量化策略长期有效的根本。