12.5 交叉验证与泄露:金融里最容易踩的雷
课前导读
本章目标
适合读者:已经理解机器学习基础、训练集、测试集、过拟合和回测概念的 A 股投资者。
本节重点:你将学会判断普通 K 折为什么可能误导金融模型,理解时间顺序、purge、embargo 的直觉,并用泄露清单审查自己的策略研究。
学习难度:advanced
预计用时:约 40 分钟
在量化研究里,模型复杂不一定危险。
真正危险的是:你以为模型在预测未来,其实它已经偷偷看过未来。
这类问题叫“泄露”。
它像考试前有人把答案夹在草稿纸里。
考出来的分数再高,也不能说明学生真正会了。
金融机器学习的验证,首先不是问“模型多聪明”。
而是问“它有没有在公平考场里答题”。
知识正文
1. 类比:金融验证像给未来考试封卷
先想一个生活场景。
老师要检验学生是否真正掌握知识。
如果模拟题里混入了正式考试的原题,学生成绩会很好看。
但这不是能力,而是信息提前泄露。
股票预测也是类似。
我们训练模型时,输入是今天或过去的信息。
目标通常是未来一段时间的收益、涨跌或排名。
如果验证时把未来信息混入训练过程,模型分数就会虚高。
A 股里这种混入非常常见。
它不一定来自明显错误。
更多时候来自细节:复权价格、指数成分、因子标准化、财报发布时间、停牌样本处理、未来区间重叠。
所以,金融交叉验证的第一原则是:不要让未来的影子落到过去。
2. 术语:普通 K 折为什么容易失真
普通 K 折交叉验证会把样本随机分成 K 份。
每次拿其中一份验证,其余几份训练。
在图片识别、文本分类等任务里,这通常很有效。
因为一张猫图和另一张猫图之间没有明确的时间前后关系。
但金融数据不是这样。
金融数据是一条时间河流。
今天的成交、明天的涨跌、下周的波动,彼此相连。
如果把这条河流随机打散,模型可能在训练集中看到验证集之后的信息。
这就像让考生先看下周新闻,再预测今天股价。
普通 K 折在金融里主要有四类问题。
第一,时间顺序被破坏。
随机抽样可能用 2024 年数据训练,再验证 2021 年样本。
但真实投资永远只能用过去面对未来。
第二,标签区间可能重叠。
假设标签是“未来 5 个交易日收益”。
1 月 2 日的标签会用到 1 月 3 日到 1 月 9 日。
1 月 5 日的标签也会用到其中几天。
如果一个样本在训练集,一个相邻样本在验证集,答案区域就部分重叠。
第三,横截面样本会共享同一天市场环境。
同一天所有股票都受同一政策、指数波动、行业风格影响。
如果按“股票-日期”随机切分,模型可能在训练集中见过同一天别的股票。
验证结果会变得过于乐观。
第四,预处理过程也可能泄露。
用全样本均值做标准化、用全样本排序做分组、用未来成分股名单回填历史,都可能让验证集信息进入训练。
风险提示
交叉验证分数高,不等于策略可靠。 如果切分方式破坏了时间顺序,分数越高,越可能是在奖励泄露。
3. 更适合金融的直觉:按时间走,留出安全距离
金融验证更像按时间进行分段演练。
先用一段历史训练。
再用之后的一段历史验证。
然后继续往前滚动。
这种思路常被称为时间序列验证、滚动验证或前推验证。
名称不重要。
重要的是它尊重“先过去,后未来”。
下面是一个更稳妥的验证流程直觉。
graph TD
A[整理原始数据] --> B[按真实发布时间对齐]
B --> C[确定标签观察期]
C --> D[按日期切分训练区间]
D --> E[留出安全间隔]
E --> F[验证未来区间]
F --> G[记录样本外表现]
G --> H[滚动到下一段]这里的“安全间隔”就是 purge 和 embargo 想解决的核心问题。
purge 可以理解为“把边界附近可能共用答案的样本清掉”。
embargo 可以理解为“验证期之后再隔一小段,不马上拿来训练”。
不必先记英文名。
先记住直觉:当标签会看未来几天,训练和验证之间就要隔开一点。
如果目标是未来 5 日收益,训练结束日和验证开始日之间通常不能无缝相接。
因为训练区间末尾样本的标签已经用到了验证期附近的数据。
可用一句话概括:
这不是精确公式,而是检查思路。
你的标签看多远,数据发布慢多久,验证边界就要更谨慎。
4. A 股案例:预测未来 5 日收益的泄露陷阱
假设你做一个 A 股多因子模型。
输入特征包括最近 20 日涨跌幅、成交额变化、换手率、波动率和行业相对强弱。
标签是未来 5 个交易日收益率。
你希望模型每天给所有股票打分。
然后买入排名靠前的一组。
如果你直接把所有“股票-日期”样本随机做 5 折,问题就来了。
2023 年 6 月 1 日的某股票样本可能进入验证集。
2023 年 6 月 2 日同一只股票样本可能进入训练集。
两者的未来 5 日收益区间高度重叠。
模型并没有真正预测未知未来。
它只是从邻近样本里看到了答案轮廓。
再看横截面问题。
2022 年 4 月某天全市场大幅波动。
如果同一天一部分股票在训练集,另一部分股票在验证集,模型可能学到“这一天整体市场情绪很差”。
这在真实交易中并不一定违规。
但如果你宣称验证集是完全陌生的未来,就会高估泛化能力。
A 股还有一些特别需要注意的地方。
涨跌停会改变可成交性。
停牌会造成样本缺口。
ST 与退市风险会让股票池发生变化。
指数成分股会定期调整。
财报数据有公告日,不是报告期结束日当天就能用。
如果这些时间点处理不严,模型会借到未来信息。
5. 泄露清单:先审数据,再审模型
| 检查项 | 常见错误 | 更稳妥的做法 |
|---|---|---|
| 价格数据 | 使用未来复权结果直接训练 | 明确回测时点可见的价格口径 |
| 财报数据 | 按报告期结束日使用 | 按公告日或可获得日使用 |
| 指数成分 | 用当前成分股回看历史 | 使用当时真实成分或可投资股票池 |
| 标准化 | 全样本均值方差 | 每个训练窗口内单独拟合 |
| 因子分位 | 用全历史排序 | 只在当日或训练期内排序 |
| 标签 | 未来区间重叠却随机切分 | 按时间切分并留安全间隔 |
| 停牌样本 | 简单删除导致幸存者偏差 | 记录停牌、退市、不可交易状态 |
| 涨跌停 | 假设信号日一定成交 | 加入可买可卖限制 |
| 调参 | 反复看测试集 | 验证集调参,最终测试集少看 |
| 代码缓存 | 特征缓存混入全样本 | 每个窗口重新生成或严格标记时间 |
这张表比模型参数更重要。
因为只要泄露存在,复杂模型会更快地把泄露放大。
线性模型可能只是虚高一点。
树模型、集成模型、深度模型可能把泄露学得非常彻底。
最后你得到的是一台“答案记忆机”,不是预测机。
关键心得
金融机器学习里,验证设计本身就是策略的一部分。 一个普通模型配上干净验证,通常比一个复杂模型配上泄露验证更值得信任。
6. 一个简化的时间验证伪代码
下面示例不依赖真实数据。
它只展示验证边界应该怎样思考。
# 目标:用过去训练,验证未来,并在边界留出安全间隔
label_days = 5
report_delay_days = 2
safe_gap = label_days + report_delay_days
for split in time_splits:
train_end = split["train_end"]
valid_start = train_end + safe_gap
valid_end = split["valid_end"]
train_data = data[data["date"] <= train_end]
valid_data = data[(data["date"] >= valid_start) & (data["date"] <= valid_end)]
scaler.fit(train_data[features])
model.fit(scaler.transform(train_data[features]), train_data["label"])
score = model.evaluate(scaler.transform(valid_data[features]), valid_data["label"])注意两个细节。
标准化工具只在训练集上拟合。
验证集只是使用训练集学到的转换方式。
验证开始日不是训练结束日的下一天。
中间留出安全间隔,避免标签区间重叠和数据发布时间错配。
真实项目会更复杂。
但这段伪代码已经抓住了核心:所有步骤都必须站在当时能知道什么的角度。
7. 可执行练习:给你的研究做一次泄露体检
请拿一个你熟悉的策略想法做纸面审查。
不需要真实数据。
只需要把验证流程写清楚。
第一步,写出你的预测目标。
例如“用今天收盘后可见的数据,预测未来 5 个交易日收益排名”。
第二步,列出每个特征的可见时间。
行情特征通常较快可见。
财报、公告、研报类数据要特别标注发布时间。
第三步,画出训练期、间隔期、验证期。
如果标签看未来 5 天,至少说明你为什么需要或不需要安全间隔。
第四步,检查预处理是否只使用训练窗口。
包括缺失值填充、标准化、行业中性化、异常值处理和特征选择。
第五步,写下一个最容易被你忽略的泄露点。
比如“当前指数成分回填历史”或“用最终退市名单过滤样本”。
完成这五步,你已经比只追求模型分数的研究更接近真实投资。
交互实验室
纸面实验:普通随机 K 折 vs 时间前推验证
请在纸上画 12 个连续月份,标记为 M1 到 M12。
设定标签是“未来 2 个月收益”。
方案 A:随机抽取 M3、M8、M11 做验证,其余月份训练。
方案 B:M1-M6 训练,M8-M9 验证,中间 M7 作为安全间隔。
请回答下面四个问题。
- 方案 A 有没有可能让训练样本和验证样本共享未来收益区间?
- 方案 A 有没有可能使用比验证月份更晚的数据训练?
- 方案 B 的安全间隔在防什么?
- 如果财报数据平均晚 1 个月可见,方案 B 是否还需要调整?
建议答案不是背概念,而是画时间轴。
只要你能指出“训练时点能不能看到这些信息”,你就掌握了本节核心。
泄露审计模板
| 步骤 | 你的填写 | 是否通过 |
|---|---|---|
| 预测时点 | 今天收盘后还是盘中 | 待检查 |
| 标签长度 | 未来几天或几周 | 待检查 |
| 数据可见时间 | 行情、财报、公告分别何时可见 | 待检查 |
| 切分方式 | 是否按时间前推 | 待检查 |
| 安全间隔 | 是否覆盖标签期与延迟 | 待检查 |
| 预处理 | 是否只在训练期拟合 | 待检查 |
| 股票池 | 是否存在幸存者偏差 | 待检查 |
| 成交假设 | 是否考虑涨跌停、停牌、T+1 | 待检查 |
把这张表填完,再去看模型分数。
不要反过来。
核心复盘
知识清单:
- [ ] 普通 K 折会随机打散时间顺序,因此不一定适合金融时间序列。
- [ ] 当标签使用未来多日收益时,训练和验证样本可能共享答案区间。
- [ ] purge 的直觉是清掉边界附近可能互相污染的样本。
- [ ] embargo 的直觉是在验证期附近留出不立即使用的安全距离。
- [ ] 任何标准化、填充、选股池构建和特征选择都必须尊重可见时间。
- [ ] A 股研究要特别注意涨跌停、停牌、退市、ST、指数成分和财报公告日。
- [ ] 干净的验证流程通常比复杂模型更能保护投资者。
金句总结
金融机器学习最怕的不是模型不够聪明,而是验证考场不够干净。