金融中的交叉验证
随机 k 折在金融里因为三个独立原因失效。Walk-forward 修一个,净化(purging)修另一个,禁运(embargo)修第三个。CPCV 是唯一能给你路径分布的方法。
金融中的交叉验证:完整方案
交叉验证是量化机器学习里被过度自信使用最多的工具。每个量化研究员都跑过 k 折 CV,看了看平均分,就把结果叫“样本外”。他们大部分都错了。CV 分数不是样本外,它是用样本内记忆伪装成验证的度量。原因是金融数据有三个破坏独立同分布假设的性质,而标准 CV 恰好利用了这三个性质。
本文给出完整方案。我们会讲为什么标准 CV 会失败、为什么 walk-forward CV 是部分修正、为什么净化 CV 是进一步修正、为什么禁运在净化之上还是必要的,以及为什么组合净化交叉验证(CPCV)是唯一能给你有意义的路径分布的方法。
参考是 AFML 第 7 章和第 12 章。数学不深,靠的是纪律。
(López de Prado, M., Advances in Financial Machine Learning, 第 7、12 章。ISBN 978-1119482086。)
三种失效模式
标准 k 折 CV 假设观测独立同分布。金融观测不满足独立同分布,原因有三个独立的维度。
原因 1:时间泄露。
随机划分把时点 分到测试集,把 分到训练集。模型在 的数据上训练,却在 的数据上评估。模型已经看到了未来。它的“样本外”预测部分来源于本不该获得的训练数据。
在表格型机器学习里(图像分类、NLP),这没什么问题:行是独立同分布的样本,“未来”没有意义。在时间序列里,这是灾难性的。模型学到的是部署时无用的时间模式。
原因 2:自相关。
时点 的收益和 的收益相关。随机划分把大部分相邻样本放在同一个折(或者同时放在训练和测试里)。测试集和训练集没有本质区别:它只是时间上的邻居。
模型在本质上和训练集同分布的数据上评估。CV 分数变成模型插值能力的估计,而不是外推能力的估计。
原因 3:特征-标签重叠。
大多数特征基于滚动窗口计算。时点 的特征依赖 到 的数据。如果标签是 到 的 天前向收益,特征和标签在时点 上重叠。
如果你的模型在时点 上训练,它带有自己的特征和标签,而测试集包含时点 ,带有它的特征和标签,那么 的特征是用 到 的数据算出来的,其中包含 的标签。模型在训练期间已经间接看到了测试标签。
这是最难发现的一种污染,因为代码看起来一切正常。特征算得对,标签算得对,划分是随机的,CV 分数高得可疑。
(AFML, §7.1–7.4。)
Walk-Forward CV:基线修正
第一个诚实的修正。不随机,沿着时间向前推进。
Fold 1: train [2000-01 to 2010-12], test [2011-01 to 2011-12]
Fold 2: train [2000-01 to 2011-12], test [2012-01 to 2012-12]
Fold 3: train [2000-01 to 2012-12], test [2013-01 to 2013-12]
...
Fold k: train [2000-01 to 2019-12], test [2020-01 to 2020-12]
训练集扩张(或者在滚动窗口变体中保持固定)。测试集总是严格在训练集之后。未来数据不会泄露到训练里。
两种变体:
扩张窗口:训练集不断增长。当你相信更多数据有用、且分布稳定时用这个。大部分量化策略用这个。
滚动窗口:训练集始终是最近 个时期。当近期数据更相关时(分布漂移、状态切换)用这个。高频策略的典型选择。
Walk-forward 模拟实盘交易:在历史数据上训练,在未来数据上评估。CV 分数是对实盘表现远好于随机 CV 的估计。但它仍然偏乐观,原因有两个:
-
模型选择(超参数、特征、阈值)是通过看验证折决定的。验证折在严格意义上不是测试数据:研究人员已经用它做了决策。跨多个折的多重比较会放大乐观偏差。
-
Walk-forward 没有考虑折之间的交易。在实盘交易中,你会交易这个模型,付出成本,然后把模型向前滚动。回测经常忘了建模这件事。
Walk-forward 夏普 1.0 大致相当于随机 CV 夏普 2.5–3.0。这就是典型的差距。
(AFML, §7.4。)
净化 CV:处理特征-标签重叠
Walk-forward 修正了时间划分。它没有修正特征-标签重叠。
重新说一遍问题:预测 时,时点 的特征依赖 到 的数据。标签窗口是 。测试折覆盖 。
训练样本 的标签窗口是 ,如果 ,它就和测试窗口重叠。如果是这样,这个训练样本的标签被测试期数据污染了。
修正是:当测试折结束于 ,从训练集里删除所有标签窗口和测试窗口有重叠的样本。净化宽度至少是 (标签跨度),如果你还要删除那些特征依赖测试窗口数据的样本,最多可以是 。
规则:如果一个训练样本的标签窗口和测试窗口有重叠,就丢掉它。
实现:
def purged_train_times(times, test_times, embargo_pct=0.01):
"""Return times that are not in test, with embargo applied."""
# Sort by time
times = sorted(times)
test_times = sorted(test_times)
# Find embargo window after last test observation
embargo_td = (test_times[-1] - test_times[0]) * embargo_pct
# Drop observations in test
train = [t for t in times if t < test_times[0]]
# Apply embargo (drop observations within embargo of test end)
train = [t for t in train if t <= test_times[-1] - embargo_td or t >= test_times[-1] + embargo_td]
return train
净化后的 walk-forward CV 是时间序列量化机器学习的最低诚信标准。
(AFML, §7.4.2。)
禁运:另一个方向
净化处理“前”方向:标签用了测试期数据的训练样本。禁运处理“后”方向。
为什么“后”方向重要?考虑测试折结束于 。下一个训练折(滚动 CV,或者测试后重拟合)紧接着开始。时点 的特征依赖 到 的数据。时点 的特征依赖 到 的数据,也就是在测试窗口里。
如果你的模型在测试折之后立即重训练,新的训练集里有些特征是用测试期数据算出来的。标签是干净的,但特征泄露了。
修正是:测试折在 结束后,跳过接下来 天的训练数据。禁运宽度 通常等于标签跨度 ,和净化同样的逻辑,只是用在了另一侧。
净化加禁运一起给出净化后的 walk-forward CV,时间序列量化机器学习的最低诚信标准。
(AFML, §7.4.3。)
实务中的禁运
关于禁运有一个细节:它是不对称的。
测试折之前(净化):标签延伸到测试窗口的训练样本。删除它们。
测试折之后(禁运):特征是用测试期数据算出来的训练样本。删除它们。
“前”规则针对的是标签。“后”规则针对的是特征。它们覆盖了不同的泄露通道。
一个常见错误:在两边(测试折之前和之后)都施加禁运。“前”方向由净化处理,禁运在“前”方向上冗余或错误。禁运只应该施加在“后”方向。
另一个常见错误:把禁运设成固定的日历时间(例如 5 天),而不管特征窗口。禁运至少应该和最大的特征回看窗口一样宽。如果你的特征包括 60 天滚动均值,禁运至少要有 60 天。
(Hyndman, R. J., & Athanasopoulos, G., Forecasting: Principles and Practice, 3rd ed., §5.10。ISBN 978-0991577989。)
组合净化交叉验证(CPCV)
Walk-forward CV 给你一条穿过数据的路径:在 [2010, 2018] 上训练,在 [2019] 上测试;在 [2010, 2019] 上训练,在 [2020] 上测试;以此类推。结果是一串夏普比率。平均夏普是你对表现的单点估计。但没有不确定性量化。
CPCV 解决了这个问题。它通过同时抽取多个测试折来生成许多条穿过数据的路径。
算法:
- 把数据划分成 个连续组(例如 对应 6 个连续年)。
- 对 个组中每 个子集(通常 ),指定这些组为测试集。
- 剩下的 个组是训练集,在每个测试边界上施加净化和禁运。
- 在净化后的训练集上训练模型,在测试集上评估。
- 对全部 个子集重复上述过程。
当 、 时,你会得到 条路径。每条路径产出一个夏普比率。你现在有 15 个夏普的分布,不是一个单一数字。
优点:
路径分布:不是单个夏普,而是 15 个。均值是估计,标准差是不确定性。
多个回测路径:每条路径都是一次完整的回测。15 条路径合起来能让你感受到策略在各个历史时期的表现。
没有泄露:每条路径都使用净化和禁运,所以没有时间泄露。
没有多重比较偏差:你没有挑出最好的一条路径,你用了所有路径。
缺点:
计算成本:15 条路径意味着 15 次模型拟合。对慢模型(例如神经网络),成本可能高得难以接受。
非独立性:15 条路径共享训练数据。夏普比率相关,这让不确定性量化变复杂。但相关结构本身是有信息量的。
(AFML, §12.3。)
选择 和
组数 和测试集大小 是超参数。常见选择:
- 、:15 条路径,每个测试集包含 2 个连续组。
- 、:45 条路径,粒度更细。
- 、:20 条路径,测试集更大。
越大路径越多,但每个测试集更小(每条路径的统计功效低)。 越小路径越少,但每个测试集数据更多。
一个实务经验法则: 要让每组包含足够数据来训练一个稳定的模型(例如 1–2 年日频数据)。 要让每个测试集有实际意义(例如 6–12 个月)。
对大多数流水线,、 是一个合理的起点。
回测重叠问题
即使使用 CPCV,回测路径也会重叠。路径 1 可能用 [2010, 2011] 作为测试。路径 2 可能用 [2010, 2012] 作为测试。训练集大量重叠。夏普比率相关。
这没问题,只要理解相关性的含义。意思是路径不是独立的性能估计,它们是相关的估计。有效样本量小于 15。
一个粗略的修正:计算夏普比率的平均两两相关性,用 除以 。对 、,有效样本量是 。这比 15 小很多,不确定性量化会反映这一点。
(Bailey, D. H., & López de Prado, M., “The Deflated Sharpe Ratio,” Journal of Portfolio Management, 40(5), 2014。)
完整例子
设定:6 年日频数据(2019–2024),标签是 的三重障碍,特征包括 60 天动量和 20 天波动率。
CPCV 用 组(每年一组)和 个测试组(2 个连续年作为测试)。总路径数:。
对每条路径,在净化后的训练集(4 年数据,测试窗口前沿 5 天净化,测试窗口后沿 60 天禁运)上拟合模型,在 2 年的测试集上评估。
15 条测试路径是:
- [2019, 2020] 测试,在净化后的 [2021, 2024] 上训练
- [2019, 2021] 测试,在净化后的 [2022, 2024] 上训练
- [2019, 2022] 测试,在净化后的 [2023, 2024] 上训练
- [2019, 2023] 测试,在净化后的 [2024] 上训练(训练集很小)
- [2019, 2024] 测试,在净化后的 [] 上训练(无训练数据)
- [2020, 2021] 测试,在净化后的 [2019, 2022–2024] 上训练
- ……等等。
有些路径训练数据极少(例如在 [2019, 2024] 上测试就没有训练数据)。你可以选择丢掉这些路径,或者把它们当作低置信度估计。
结果:15 个夏普比率。假设均值是 1.2、标准差是 0.5。估计是“夏普在 1.2 附近,有相当大的不确定性”。
对比单个 walk-forward CV:你会有一个夏普,比如说 1.5。CPCV 告诉你这个 1.5 是众多可能值中的一个;更现实的估计接近 1.2,且置信区间更宽。
常见错误
反复出现的五种错误。
错误 1:跳过净化。
没有净化的 walk-forward 有特征-标签重叠。CV 分数向上偏。策略在实盘会表现更差。
错误 2:忘记禁运。
有净化但没禁运的 walk-forward,在后沿有特征泄露。CV 分数向上偏。策略在实盘会表现更差,特别是在状态过渡时。
错误 3:用随机 k 折。
随机 k 折同时具有三种失效模式。CV 分数没有信息量。策略在实盘会表现更差,通常差很多。
错误 4:净化过于激进。
如果你净化得超过必要的程度(例如在只需要 的时候净化 ),你就在丢失训练数据的同时,并没有进一步减少泄露。CV 分数会变得方差更大,但不会更诚实。
错误 5:用 CV 做超参数选择,但没有留出最终测试集。
如果你把整个数据集都用于 CV 超参数选择,胜出的超参数就是最贴合历史数据的那些,包括你想去掉的所有泄露模式。最终测试应该是一个从未用于任何决策的 holdout 集。
CV 能告诉你什么、不能告诉你什么
CV 能告诉你:
- 模型在样本外数据上的预期表现,前提是数据生成过程平稳。
- 这个表现在不同历史路径上的方差。
- 在你的数据上,一个模型是否比另一个明显更好。
CV 没法告诉你:
- 策略在未来会不会表现好(数据生成过程可能变化)。
- 策略能不能扛住交易成本(CV 通常不建模成本)。
- 策略能不能扛住容量限制(CV 假设你能按预测价格成交)。
诚实的说法是:CV 是模型选择工具,不是部署决策工具。一个赢得 CV 的模型只是部署的候选。是否部署取决于其他因素(容量、成本、对状态变化的稳健性),CV 不管这些。
(López de Prado, M., Advances in Financial Machine Learning, 第 7、12 章。ISBN 978-1119482086。)