禁运效应
净化处理前缘,禁运处理后缘。两者合在一起构成对特征-标签重叠的完整防御。跳过禁运,CV 仍然在撒谎。
禁运效应:单靠净化不够
净化删除标签延伸到测试折的训练观测。它处理前缘泄露:因为标签窗口前向延伸到测试期而污染的训练样本。净化讲清楚了,大部分实现都做。
禁运是另一半。它处理后缘泄露:测试折之后的训练观测,特征向后延伸到测试期。没有禁运,在测试之后的数据上训练出来的模型,所用的特征是用测试期数据算出来的。标签是干净的,特征不是。CV 分数向上偏,策略在实盘会表现更差。
本文讨论为什么禁运是必要的,宽度怎么设,它和净化怎么交互。数学和净化的推导(上一篇文章)一样,但纪律不同:大部分从业者跳过禁运,因为泄露是隐形的。
(López de Prado, M., Advances in Financial Machine Learning, §7.4.3。ISBN 978-1119482086。)
后缘泄露
想象时间轴。测试折从 到 。训练数据既存在于 之前,也存在于 之后。
考虑时点 的训练观测。它的特征是用 的数据算的。如果 ,特征窗口就向后延伸到测试折。特征被污染了。
举个例子,假设特征是 20 天移动平均。在 时,特征窗口是 。这个窗口包括 到 (测试折之后)和 到 (测试折开始之前和当时)。等等, 是测试折的结束,所以窗口 包含 ,这部分和测试折 重叠。
从 到 的 5 天在测试折内(假设 ,也就是测试折至少有 16 天)。 时的特征被污染了。
时的标签是干净的(它的标签窗口是 ,不与测试折重叠)。只有特征泄露。
这就是后缘泄露。净化抓不到(净化只处理前缘的标签泄露)。禁运能。
禁运规则
删除下面区间内的训练观测:
其中 是数据集中最大的特征回看。禁运宽度是 。
有一个细节:禁运应该施加在“特征窗口会向后延伸到测试折”的观测上。严格规则是:如果 就删除 。对训练观测 ,这等价于 。所以禁运区间是 。
宽度恰好是 ,等于特征回看。
(AFML, §7.4.3。)
具体例子
设定:60 天动量特征,5 天持仓期,日频观测。测试折是 252 天(一个交易日年)。
时点 的标签依赖 的数据。时点 的特征依赖 的数据。测试折是 ,。
前缘: 内的训练观测,标签窗口延伸到测试折。这些要被净化。净化宽度是 5。
后缘: 内的训练观测,特征窗口向后延伸到测试折。这些要被禁运。禁运宽度是 60。
总共“损失”的训练观测:。对一个 252 天的测试折,这是测试折大小的 26%。训练集减少了 65 天。
对 10 年数据集(约 2,520 个交易日),损失是 65/2,520 = 2.6%。不大。但它阻止的泄露是有意义的:没有禁运,测试之后的训练观测的特征会被测试期数据污染,CV 分数向上偏,偏的幅度未知。
禁运最关键的时候
三种场景。
场景 1:长特征窗口。
如果你的特征包括 250 天移动平均,禁运就是 250 天。这在典型 252 天测试折里占相当大比例。禁运在后缘会让训练数据减少大约 50%。
场景 2:状态过渡。
如果测试折包含状态切换(例如金融危机),测试之后的数据有用了危机期数据算出来的特征。在这个数据上训练的模型被危机模式污染了。禁运能防止这一点。
场景 3:重拟合场景。
如果测试折结束后你重拟合模型,新的训练集包括禁运区。没有禁运,新模型的特征会“记住”测试折。禁运防止模型隐式记忆测试期。
三种情况下,禁运对诚实的 CV 都是必要的。
禁运不那么重要的时候
三种场景中,禁运的影响小一些。
场景 1:walk-forward CV,测试折内不重训练。
如果你在 上训练一次,在 上评估,那么测试之后没有训练数据。禁运不适用。
这是典型的 walk-forward 设置。禁运主要对滚动 CV 和重拟合场景重要。
场景 2:短特征窗口。
如果最大特征窗口只有 5 天,禁运是 5 天。数据损失极小。
场景 3:长测试折。
如果测试折是 1000 天,60 天的禁运是测试折大小的 6%。相对影响小。
与重拟合的交互
禁运最常见的应用场景:重拟合。
假设你在数据 结束处训练,在 上评估,然后在数据 结束处重拟合。重拟合训练集在 有数据,这些数据的特征向后延伸到 。重拟合的模型被污染了。
修正是:在重拟合训练集里跳过禁运区。重拟合模型在 上训练,禁运区被排除。
代码:
def embargo_post_test(train_times, test_end, w):
"""Drop training observations in [test_end + 1, test_end + w]."""
embargo_start = test_end + 1
embargo_end = test_end + w
return train_times[(train_times < embargo_start) | (train_times > embargo_end)]
一个常见错误:在多折 CV 方案的每一个测试折之后忘了施加禁运。如果你有 5 个测试折,就有 5 个禁运区。每个都必须从对应的训练集中排除。
净化和禁运的不对称
这两种机制形式类似,但精神不同。
净化:处理标签泄露。时点 的标签依赖 的数据。如果这个窗口和测试折相交,标签被污染。污染的是训练观测的结果。
禁运:处理特征泄露。时点 的特征依赖 的数据。如果这个窗口和测试折相交,特征被污染。污染的是训练观测的输入。
两者都是真实的,都让 CV 分数向上偏,都必须处理。
不对称在于方向。净化删除测试之前的训练数据。禁运删除测试之后的训练数据。它们互为镜像,但数学不同,因为标签窗口向前延伸(到 ),特征窗口向后延伸(到 )。
禁运宽度:应该多大?
严格规则:禁运宽度至少要等于数据集中最大的特征回看 。
一个有用的诊断:列出数据集中所有特征以及它们的回看窗口。最大值就是 。把禁运设为 。
更精确的规则:按特征设定禁运。对每个特征,检查它的窗口是否向后延伸到测试折。如果是,删除观测。计算量更大但更精确。
高频策略的一个经验法则:用测试折的某个百分比。252 天的测试折,60 天的禁运是 24%。21 天的测试折,5 天的禁运也是 24%。百分比方法会按测试折大小缩放。
(AFML, §7.4.3。)
百分比启发法
更简单的规则:把禁运设为测试折大小的某个百分比,比如 1% 或 5%。这是会随测试折大小缩放的经验法则。
252 天的测试折用 1% 的禁运,禁运是 2.52 天,取整为 3 天。这比典型 60 天动量特征窗口要窄。启发法对很多场景都太短。
252 天的测试折用 5% 的禁运,禁运是 12.6 天,取整为 13 天。还是比 60 天短。
百分比启发法对短特征窗口的高频策略有效。对长窗口的波段或仓位策略会失效。
更安全的规则:取(测试折的百分比,)两者的最大值。
禁运与并发
进一步修正:当很多观测有重叠标签窗口时,测试之后训练数据的独特信息量很小。禁运应该考虑这一点。
和净化(上一篇文章)中一样的并发调整:
其中 是标签的平均并发数。对 的日频三重障碍标签, 大约是 5。对 ,调整后禁运是 300 天。这比严格 更激进。
理由是:如果每条测试后观测的标签和 个其他标签重叠,跨 个标签的复合特征泄露大于简单 。调整后的禁运考虑到了这一点。
(AFML, §4.4 和 §7.4.3。)
禁运与 CPCV
在 CPCV(组合净化交叉验证)中,每条穿过数据的路径有多个测试折。禁运必须施加在每个测试折的边界上。
一个有用的心智模型:对每个测试折 ,在 上净化,在 上禁运。再把所有测试折的禁运区取并集。剩下的时段就是训练集。
6 组、每条路径 2 测试组的 CPCV,有 15 条路径,每条有 2 个测试折。所有禁运区的并集可能覆盖了相当大一部分数据。
实务中要注意:某些观测可能被多个测试折禁运。这没问题,它们就是不在训练集里。训练集更小,但更干净。
(AFML, §12.3.2。)
常见错误:在两边都禁运
一个常见错误:在测试折两边(之前和之后)都施加禁运。这不对。
“之前”一侧由净化处理,不是禁运。净化处理标签泄露(标签延伸到测试折的训练观测)。在“之前”侧用禁运会用不同的机制处理同样的泄露,但是冗余的。
禁运专门针对测试之后的数据:特征向后延伸到测试折的训练观测。在“之前”侧施加禁运没有意义。
如果你的实现在两边都禁运,你是在过度激进:删除了本该保留的观测。
常见错误:重拟合时忘了禁运
常见错误:在测试折结束后重拟合时,施加了净化但忘了禁运。
想法是:“测试折已经结束了,禁运不相关。”但禁运针对的是测试之后的训练数据,下一次重拟合就会用到。如果重拟合时跳过禁运,新模型的特征就被测试期数据污染了。
修正是:在重拟合场景中始终施加禁运。禁运区与初始训练时一样:。
禁运与在线学习
在线学习中(后续文章会讲),模型会随新数据到来不断更新。禁运自动得到处理:测试之后的数据在特征不再向后延伸到测试期之前,都不会用于训练。
实际中,在线学习系统维护一个训练数据的滑动窗口。窗口至少要有 宽,才能保证没有时间泄露。“禁运”隐含在窗口结构里。
一个有用的诊断
分别计算带禁运和不带禁运的 CV 分数。差异就是泄露偏差的估计。
如果差异小(例如有禁运夏普 1.5,没禁运夏普 1.55),泄露就小。策略可能比较稳健。
如果差异大(例如有禁运夏普 1.0,没禁运夏普 2.0),泄露就很大。策略的“真实”CV 分数远低于朴素 CV。没有禁运,你会部署一个实盘表现少 50% 甚至更多的策略。
在你用的每个 CV 方案上都跑这个诊断。如果差距大,就修禁运。如果差距小,这个数据集的禁运就不那么要紧。
禁运在 Walk-Forward 与滚动 CV 中
禁运的重要性取决于 CV 方案。
Walk-forward CV(扩张或滚动训练窗口,单向前测):测试折在样本末尾。测试之后没有训练数据。禁运不适用。
带周期性重拟合的滚动 CV:模型在每一步重拟合,使用直到当前日期的数据。测试之后的训练数据(之前测试折之后)用来拟合下一次迭代。禁运适用:跳过特征会向后延伸到之前测试折的后测区。
组合净化 CV(CPCV):多个重叠的测试折,每个都需要自己的禁运。所有禁运区的并集可能覆盖相当一部分数据。分别对每个测试折算禁运,然后取并集。
一个常见实现模式:
def cv_with_embargo(data, test_folds, h, w):
"""Apply purge and embargo for each test fold."""
embargoed_indices = set()
for test_start, test_end in test_folds:
# Purge: leading-edge training observations
purge_start = test_start - h
purge_end = test_start
embargoed_indices.update(range(purge_start, purge_end))
# Embargo: trailing-edge training observations
embargo_start = test_end + 1
embargo_end = test_end + w
embargoed_indices.update(range(embargo_start, embargo_end))
# Mask out embargoed observations
mask = np.ones(len(data), dtype=bool)
mask[list(embargoed_indices)] = False
return data[mask]
这个函数返回禁运索引被屏蔽后的数据。
泄露预算
一个有用的诊断:估算没有禁运时会发生多少泄露。
252 天的测试折,、:
- 净化宽度:5 天。
- 禁运宽度:60 天。
- 总共“损失”的观测:65。
没有禁运时,60 个测试后观测的特征会用到测试期数据。泄露的大小取决于污染特征和测试折结果之间的相关性。
实际中,对稳定的特征窗口(例如 20 天动量),泄露很小;对长窗口(例如 250 天移动平均),泄露很大。对 250 天移动平均,250 天的禁运能防止大量泄露。
一个粗略的经验法则:没有禁运时的泄露与特征窗口大小 成正比。 越大,泄露越大,禁运越重要。
(AFML, §7.4.3。)
禁运与样本权重
禁运删除训练观测。样本权重(来自样本权重一文)对剩下的观测降权。它们是互补操作。
典型的流水线:
- 施加禁运删除泄露观测。
- 施加唯一性权重处理重叠标签。
- 施加时间衰减处理状态漂移。
- 施加波动率缩放处理绝对收益异质性。
顺序很重要:先禁运(数据最干净),再唯一性(处理标签重叠),再时间衰减和波动率缩放(处理状态和绝对收益问题)。
禁运后训练集比原始小。加权后训练集大小不变,但观测权重不同。两个都是金融数据诚实机器学习所必需的。
(AFML, 第 4 章和第 7 章。)
禁运给你带来什么
一个诚实的 CV 分数。没有禁运,CV 分数会因特征泄露向上偏。有了禁运,分数更接近实盘表现。
改进通常让夏普降低 5%–20%(无偏夏普低于有偏夏普)。一个没有禁运看起来夏普 2.0 的策略,加了禁运后可能显示夏普 1.7。2.0 是乐观的,1.7 更接近现实。
代价是数据:禁运会从训练集中删除观测。对大多数流水线,代价不大(几个百分点的数据)。对特征窗口特别长(例如 250 天)的流水线,代价更大(最多可达测试折后缘大小的 50%)。
权衡是:更诚实的 CV(有禁运)vs 更多的数据(无禁运)。对大多数流水线,诚实的 CV 值得数据代价。第一次发现加了禁运的 CV 不同意未加禁运的 CV,你会庆幸当初做了禁运。
(López de Prado, M., Advances in Financial Machine Learning, §7.4.3。ISBN 978-1119482086。)