跳转到内容

金融交叉验证

随机 k 折为什么在金融里是错的

Section titled “随机 k 折为什么在金融里是错的”

时间序列样本高度相关:tt 日的样本与 t+1t+1、t+2t+2 日几乎携带同样的信息。随机 k 折把相邻样本分进训练集与测试集,等于让训练集"看到"测试集的邻居——模型只要记住"昨天的标签",就能在测试集上表现优异。金融数据上随机 k 折的高分几乎都是泄漏,且高估幅度可以夸张到完全虚假。

两个修正:

  1. 清洗(purging):从训练集中剔除标签窗口与测试集重叠的样本——用三重障碍法时,标签窗口长达数十天,重叠必须显式清除;
  2. 禁运(embargo):测试集之后再空出一段(如标签窗口长度或 1% 样本)——特征工程若含滚动统计(波动率、均线),测试集之后的数据会通过特征泄漏给训练集。
purged-cv.py
import numpy as np
def purged_kfold_indices(T: int, label_span: int, k: int = 5, embargo: int = 0):
"""时间顺序 k 折 + 清洗 + 禁运。label_span: 标签窗口长度"""
folds = np.array_split(np.arange(T), k)
for i, test_idx in enumerate(folds):
test_lo, test_hi = test_idx[0], test_idx[-1]
train_idx = []
for j, f in enumerate(folds):
if j == i:
continue
f = f[f + label_span - 1 < test_lo] # 清洗:标签窗口不伸入测试集
f = f[f > test_hi + embargo] # 禁运:测试集之后空出一段
train_idx.append(f)
yield np.concatenate(train_idx), test_idx
  • Walk-forward:训练在 [1,t][1, t],测试在 [t+1,t+h][t+1, t+h],滚动前进。最贴近实盘,但只有一条路径——一个时间轴上的一次滚动,结论对"恰好这段样本"敏感;
  • 组合清洗交叉验证(CPCV):把样本切成 NN 块,取 kk 块做测试、其余做训练的所有组合——多条路径,能同时给出性能分布与过拟合概率,是 López de Prado 对 walk-forward 的改进。

CPCV 的输出不只是平均分数,而是分数分布:

  • 多条路径上的表现差异大(如夏普从 0.3 到 1.5)→ 高度过拟合;
  • 把观察到的最优分数放进 CPCV 分数分布中排名,得到过拟合概率(PBO)——与第一卷 Deflated Sharpe Ratio 是同一思想的另一实现。
  • 所有 ML 评估用带清洗 + 禁运的时间顺序 CV,随机 k 折直接禁止;
  • 最终评估用 CPCV,报告分数分布而非单点;
  • walk-forward 保留为"最贴近实盘"的最后一道检验。
  • 清洗:剔除与测试集标签窗口重叠的训练样本
  • 禁运:测试集之后再空出一段
  • Walk-forward 只有一条路径,CPCV 提供多条