跳到正文

回测过拟合概率(PBO):怎么知道你那条最佳策略大概率是最烂的

回测过拟合概率(PBO)回答的是最让人不舒服的那个问题:被我选中的那条策略,有多大概率其实比中位数还差,尽管它的回测看起来最好?

第 2 篇 · 共 8 篇 约 12 分钟

按回测最好表现选策略,等于保证你选的是”幸运的幸存者”。PBO 量化的是你可能不幸到什么程度。你跑了 50 个策略变种、挑了 Sharpe 最高那条,它实际上比 50 个里的中位数还差的概率是多少?如果你的答案是”概率很低,我挑了最好的那个”,那你错了。诚实的答案有时是 30%,有时是 50%,有时更高。

这篇文章讲 López de Prado 的回测过拟合概率(PBO)怎么工作、走过一遍驱动它的组合式对称交叉验证(CSCV)算法,并且给你一个带具体数字的例子。这里面有个坑让量化机构真的亏过钱。我会告诉你它是什么、怎么避开。

设置

你有 NN 个候选策略。它们可能是同一信号的多个超参、同一资产上的多个信号、或者特征和规则的多种组合。你在同一份回测上跑了所有这些。你有 NN 个 Sharpe 比率。

你挑了 Sharpe 最高的那条。你准备上线了。

这条策略——你回测竞赛的赢家——实际上比你候选集合里的中位数还差的概率是多少?

那个概率就是 PBO。它回答的问题是”我挑的是赢家,还是看着最像赢家的输家”。

直觉很直接。如果你抛 50 枚硬币、其中一枚连续 10 次正面,你怀疑那枚硬币是做过手脚的。但如果你抛 50 枚硬币、其中一枚连续 6 次正面,那就不稀奇。看起来最好的那枚硬币实际上是不是有偏,取决于最好的结果有多极端、你抛了多少次。

PBO 把这个推广到策略。“试验”是你的候选策略。“最好的结果”是最大 Sharpe。“偏”指的是策略是真好还是纯运气。

为什么最佳 Sharpe 是误导人的

考虑最简单的情况。你有 100 个策略,每个的真实 Sharpe 都是 0(纯噪声)。你在 5 年数据上回测每个。靠运气,其中一些会有正 Sharpe。零假设下最佳 Sharpe 的分布是已知的。

NN 个零 Sharpe 策略、TT 年回测数据、日收益下的期望最大 Sharpe 大约是:

E[max⁡Sharpe]≈V[Sharpe]⋅(2log⁡N−log⁡(log⁡N)+log⁡(4π)22log⁡N)\mathbb{E}[\max \text{Sharpe}] \approx \sqrt{V[\text{Sharpe}]} \cdot \left(\sqrt{2 \log N} - \frac{\log(\log N) + \log(4\pi)}{2\sqrt{2\log N}}\right)

N=100N=100 个日频策略,这个值算出来大约是 1.0。所以如果你的最佳 Sharpe 是 1.0,你可能只是 100 个噪声策略里运气最好的那个。如果你的最佳 Sharpe 是 2.0,你可能在看一个真实的 edge。或者你可能在看 1000 个而不是 100 个策略里运气最好的那个。

PBO 把这件事形式化了。它不要求你指定零分布。它从你自己的数据里推出来。

CSCV:算法

组合式对称交叉验证(CSCV)是 PBO 背后的引擎。它作用在你的候选策略的收益上。

第一步:收集所有 NN 个候选策略的回测收益序列。每条序列有 TT 个观测(日、周等)。

第二步:把 TT 个观测切成 kk 个连续时间块。块 1 包含前 T/kT/k 个观测,块 2 接下来 T/kT/k 个,等等。日频数据用几年的话用 k=16k=16。

第三步:对每个子集 S⊆{1,2,...,k}S \subseteq \{1, 2, ..., k\},满足 ∣S∣=k/2|S| = k/2,只用 SS 里的观测算出每个策略的回测。记为策略 ii 的 SharpeiS\text{Sharpe}_i^S。

第四步:按 SharpeiS\text{Sharpe}_i^S 给策略排序。找出 SS 中样本内 Sharpe 最高的那条。

第五步:只用不在 SS 里的观测算每条策略的样本外 Sharpe。记下样本内赢家在那些 OOS 表现里的相对排名 ωS\omega_S,其中 ωS<0.5\omega_S < 0.5 表示该赢家排名低于样本外中位数。

第六步:对所有 (kk/2)\binom{k}{k/2} 个子集 SS 重复。

现在每个子集都有一个样本内赢家选择对应的 OOS 相对排名。PBO 不是数 OOS Sharpe 是负的那些:它数的是样本内赢家排名落在样本外中位数以下的占比。

PBO 定义为:

PBO=#{S:ωS<0.5}(kk/2)\text{PBO} = \frac{\#\{S : \omega_S < 0.5\}}{\binom{k}{k/2}}

它是”样本内赢家在样本外跑输中位数”的子集占比。PBO 0% 意味着在所有这些切分里被选出的赢家从来不会排在样本外中位数之下。PBO 50% 意味着按这个诊断指标看你的选择过程并不比随机更好。PBO 90% 意味着它经常在挑一个低于中位数的样本外策略。

正式参考是 López de Prado, Advances in Financial Machine Learning, §11.6(Strategy Selection / CSCV)(ISBN 978-1-119-48208-6),建立在 Bailey, Borwein, López de Prado 和 Zhu(2017),“The Probability of Backtest Overfitting,” Journal of Computational Finance, 20(4), pp. 39-70 的基础上。

一个具体例子

假设你在 8 年日频 S&P 500 数据上跑了 50 个动量策略变种。每个变种用不同的 lookback 窗口(20, 25, 30, …, 365 天)和不同的持仓期(1, 5, 10, 20 天)。网格给你 50 个策略。

你挑了 Sharpe 最高的那条:60 天 lookback、10 天持仓。回测 Sharpe 1.8。

现在你用 k=16k=16 块跑 CSCV。那就是 (168)=12,870\binom{16}{8} = 12{,}870 个子集。对每个子集,你找出最佳样本内策略、算它的样本外 Sharpe。

结果可能是:

  • 7,200 个子集:样本内赢家的 OOS 相对排名等于或高于中位数。
  • 5,670 个子集:样本内赢家的 OOS 相对排名低于中位数。
PBO=567012870=44%\text{PBO} = \frac{5670}{12870} = 44\%

PBO 44% 是警报级别的。它意味着在 44% 的 CSCV 子集里,因为样本内看着最好而被挑出来的那条策略,其样本外排名落在了中位数之下。你在这个挑选过程下挑到中位数以下的策略的概率差不多一半一半。

这不是假设情形。我见过在单次回测上看很漂亮的量化策略 PBO 达到 60–80%。策略本身不差。挑选方法差。

老实的陷阱

这是个没人警告过你的陷阱。CSCV 告诉你你的挑选方法过拟合的概率。它不告诉你你挑的那条策略是不是好的。

假设你的 PBO 是 10%。这很好。它意味着你的挑选过程在大概 10% 的 CSCV 子集中挑了中位数以下的样本外策略。但你还是挑了一条具体的策略。PBO 不告诉你那条具体策略好还是不好;它诊断的是挑选过程。

PBO 是关于方法的,不是关于具体那一挑的。

诚实的框架是这样的:如果 PBO 高,你的挑选方法坏了。修方法(少跑几个策略、更强的先验、更好的数据)。如果 PBO 低,你的挑选方法 sound,你可以信你的那一挑——剩余的不确定性由 PBO 这个数本身捕捉。

你不应该做的是:挑那条在 CSCV 路径上 OOS Sharpe 最好的策略。这就是换了皮的 k-fold CV,你又回到过拟合了。

实现细节

CSCV 需要每条候选策略的完整收益序列。这比只算 Sharpe 比率要重。你需要每条策略的日(或周)P&L。

具体来说:

import numpy as np

def cscv(returns_matrix, k=16):
    # returns_matrix: shape (T, N), N candidate strategies, T observations
    n_obs, n_strats = returns_matrix.shape
    block_size = n_obs // k
    blocks = [returns_matrix[i*block_size:(i+1)*block_size] for i in range(k)]
    
    from itertools import combinations
    subsets = list(combinations(range(k), k // 2))
    oos_sharpes = []
    
    for S in subsets:
        in_sample = np.concatenate([blocks[i] for i in S], axis=0)
        is_sharpes = in_sample.mean(axis=0) / in_sample.std(axis=0)
        best_strat = np.argmax(is_sharpes)
        oos_data = np.concatenate([blocks[i] for i in range(k) if i not in S], axis=0)
        oos_sharpe = oos_data[:, best_strat].mean() / oos_data[:, best_strat].std()
        oos_sharpes.append(oos_sharpe)
    
    pbo = np.mean(np.array(oos_sharpes) < 0)
    return pbo, oos_sharpes

提示词

用 NumPy 实现 CSCV,从多条策略收益里选出样本内表现最好的策略,再计算样本外表现和 PBO。

这是核心算法。完整版要处理块边界处的 purged 观测(见前一篇关于交叉验证方法的文章)、正确做年化、并考虑收益序列的自相关。

实际实现用 Arithmion SDK 的 CSCV 原语。它们处理 purging、块构造和子集上的并行化。手动跑 (168)=12,870\binom{16}{8} = 12{,}870 个子集也是快速烧掉一周 CPU 时间的方式。

阈值解读

你应该接受什么 PBO 值?

低于 10%:你的挑选方法极好。可以带着对方法(而不是具体那一挑)的信心部署。 10–25%:你的挑选方法 sound。谨慎部署。具体策略仍有 10–25% 概率是输家。 25–50%:你的挑选方法有问题。重新考虑你是不是跑了太多候选策略、或者超参过拟合了。 高于 50%:你的挑选方法坏了。你大部分”最佳”策略都是运气。砍掉候选集、加强先验、拿更多数据。

25% 这个阈值最重要。再往上,你基本上就是在扔硬币挑策略,不应该基于这种方法部署。

怎么降低 PBO

能把 PBO 拉下来的五个动作:

1. 减少候选策略数量。 你从 100 个策略降到 10 个,PBO 急剧下降。试很多变种的代价直接反映在 PBO 里。

2. 用更强的先验。 不要在任意的超参数网格上搜。用你对市场微观结构的了解来约束搜索。如果你的先验说 lookback 应该在 20–100 天,就别测 5 天也别测 500 天。

3. 在每个候选内用 purged 交叉验证。 如果你的候选由于标签泄露本身就过拟合,PBO 会高。用 purged CV 诚实地评估每个候选,再在诚实的 Sharpe 上跑 CSCV。

4. 加样本外数据。 如果你有 10 年数据、你在这 10 年上都评估了,PBO 很高。留出 2 年作为最终测试集。在 8 年样本内数据上跑 CSCV。然后在 2 年样本外数据上测赢家。

5. 用 Deflated Sharpe Ratio。 Bailey 和 López de Prado(2014)的 Deflated Sharpe Ratio(DSR)会根据试验次数、收益自相关和尾部风险调整你报的 Sharpe。DSR < 0 意味着经过多重检验校正后 Sharpe 在统计上不显著。报 DSR 的时候跟原始 Sharpe 一起报。

Arithmion SDK 包含 PBO、CSCV 和 DSR 的计算。在你把真钱放到任何一条策略上之前都跑一遍。

与多重检验的关系

PBO 是一种多重检验校正。如果你跑一条策略,你的 Sharpe 需要跨过一个显著性阈值。如果你跑 100 条策略,你的 Sharpe 需要跨过 100 个阈值。大部分量化机构忽略这件事。CSCV 强制执行这件事。

数学体现在 Bonferroni 风格调整里:如果你想要跨 NN 次试验 5% 的假阳性率,每次试验需要 0.05/NN 的显著性阈值。N=100N=100,每次试验需要 p<0.0005p < 0.0005。这个门槛很高。

CSCV 比 Bonferroni 没那么保守(它利用策略之间的相关性),但应用同样的逻辑。如果你跑了 100 个策略,你需要比只跑 1 个强得多的证据。

一个真实的失败案例

我合作过的一家量化机构在 8 个资产类别上跑了 500 个均值回复策略变种。他们挑了 Sharpe 最高的,那是在 3 年回测上 Sharpe 2.4。他们用 5000 万美元部署了。

在 500 个策略上跑 CSCV 返回的 PBO 是 67%。最佳策略有 67% 的概率比中位数还差。他们忽略了警告。

6 个月之内,策略跌了 18%。实盘 Sharpe 0.3。他们把它关了。

问题不在策略。问题在于他们没办法区分一个真的 2.4 Sharpe 和 500 次试验里的一个幸运 2.4 Sharpe。CSCV 告诉他们了,他们没听。

教训:在你爱上某次回测之前先跑 CSCV。

PBO 没告诉你的事

PBO 是样本内诊断。它用你的回测数据估计过拟合概率。它不告诉你:

  • 你的策略在下一个 regime 里能不能 work。
  • 你的交易成本假设现不现实。
  • 你的容量是不是受流动性限制。
  • 你的数据有没有幸存者偏差。

这些是分开的问题,有分开的解决方案。PBO 解决一个具体的问题:你在挑的是最佳策略还是运气最好的那个?

如果 PBO 低并且你已经处理了其他关注点(regime 稳定性、交易成本、容量),那就部署。如果 PBO 高,先修你的挑选方法。

诚实的总结

PBO 是量化金融里最让人不舒服的指标。它告诉你你最好的回测比中位数还差的概率。大部分 quant 算都不算。大部分算过的 quant 算了也不理。认真对待它的人,救了自己不去部署那些看着漂亮但会挂的策略。

最低限度可行的工作流:

  1. 生成 NN 个候选策略。
  2. 在同一份数据上回测每个。
  3. 用 k=16k=16 跑 CSCV。拿到 PBO。
  4. 如果 PBO > 25%,停。砍 NN 或加强先验。
  5. 如果 PBO < 25%,再跑 DSR。检查最佳 Sharpe 是否跨过 DSR > 0。
  6. 如果两个都过,把 20% 的数据留出来作为最终测试。在留出数据上跑赢家。
  7. 如果留出 Sharpe 跟样本内 Sharpe 大致在同一档位,部署。

这不快。这不便宜。但这是判断你的策略是真的还是运气所必需的。

PBO 的推导

正式推导走几步,如果你想正确解读 PBO 的话值得理解。

CSCV 把数据切成 kk 块,对 (kk/2)\binom{k}{k/2} 个子集中的每一个算出样本内赢家在所有候选策略 OOS 表现中的相对排名 ωˉS∈(0,1)\bar\omega_S \in (0,1)。logit 变换后的排名,

λS=log⁡(ωˉS1−ωˉS),\lambda_S = \log\left(\frac{\bar\omega_S}{1 - \bar\omega_S}\right),

在”样本内赢家不比随机好”的零假设下近似服从正态分布。这个分布的均值和方差从数据中估计。

PBO 定义为 Pr⁡[λ<0]=Pr⁡[ωˉ<0.5]\Pr[\lambda < 0] = \Pr[\bar\omega < 0.5]:样本内赢家排在样本外中位数以下的概率。经验 PBO(ωˉ<0.5\bar\omega < 0.5 的子集占比)在子集数变大时收敛到理论 PBO。

这个近似在以下条件成立时效果不错:

  • 块数 kk 至少是 16。
  • 块大小至少 60 个观测。
  • 策略收益自相关低。

当这些条件不满足时,PBO 可能被错估。5 年以上的日频数据,k=16k=16、块大小约 125 天效果不错。

PBO 与多重检验校正

PBO 是几种多重检验校正中的一种。经典替代:

Bonferroni 校正:把显著性阈值除以 NN(试验数)。N=50N=50 个策略、5% 目标假阳性率,每次试验必须跨过 p<0.001p < 0.001。保守但简单。

Holm 校正:step-down 程序,没 Bonferroni 那么保守。对第 ii 小的 pp 值如果 p<α/(N−i+1)p < \alpha / (N - i + 1) 就拒绝零假设。比 Bonferroni 更有检验力。

Benjamini-Hochberg(BH)校正:把错误发现率(FDR)控制在 α\alpha 水平。以一定比例的假阳性为代价允许更多发现。N=50N=50 个策略、FDR = 10%,你可以接受最多 10% 的发现是假的。

PBO 比 Bonferroni 和 Holm 更有检验力,因为它利用了策略之间的相关性。共享特征或超参的策略有相关的收益,所以独立试验的有效数量小于 NN。PBO 抓住了这一点。

BH 是 PBO 的天然补充。跑 PBO 评估你的挑选方法。跑 BH 控制你接受的策略里期望的假发现数。两个都用。

Deflated Sharpe Ratio 的联系

Bailey 和 López de Prado(2014)的 Deflated Sharpe Ratio(DSR)是 PBO 的天然对应物。PBO 问”我是不是挑了个幸运的输家?“的时候,DSR 问”我最佳的 Sharpe 是不是在零假设期望最大值之外有统计显著性?”

在 AFML 公式化里,DSR 考虑到:

  • 试验次数 NN 和样本长度 TT 通过期望最大值基准 SR∗SR^*。
  • 收益分布的偏度 γ3\gamma_3 和峰度 γ4\gamma_4。
  • Sharpe 估计的抽样不确定性。

AFML 的 CDF 之前统计量是:

zDSR=(SR^−SR∗)T−11−γ3SR^+γ4−14SR^2z_{DSR} = \frac{(\widehat{SR} - SR^*)\sqrt{T-1}}{\sqrt{1 - \gamma_3\widehat{SR} + \frac{\gamma_4 - 1}{4}\widehat{SR}^2}}

DSR 概率是:

DSR=Φ(zDSR)\text{DSR} = \Phi(z_{DSR})

在计算时让 Sharpe 和 SR∗SR^* 用同一种收益频率。这里的 γ4\gamma_4 是峰度,所以对正态收益 γ4=3\gamma_4 = 3;它不是超额峰度。显示的 zDSRz_{DSR} 是一个 z 统计量,不是 DSR 本身。自相关在 AFML 这个表达式里不是直接一项;如果需要依赖性修正,要单独处理。

按概率约定,DSR > 0.95 是一个常用的证据门槛。DSR < 0.95 表示结果还没跨过那条概率门槛。

用 PBO 评估挑选方法。用 DSR 评估最佳策略的显著性。两者一起告诉你该不该部署。

跨策略类型解读 PBO

PBO 在不同策略类型上表现不同。经验观察:

均值回复策略(短 horizon):PBO 通常适中(15–30%),因为合理的超参数设置数量少。搜索空间被经济意义能成立的东西所约束。

动量策略(中 horizon):PBO 通常高(30–60%),因为 lookback 和持仓期的搜索空间大。Quant 一般测 50–200 个组合。

横截面股票策略:PBO 差异巨大。有强经济学理由的策略(价值、动量、carry)PBO 低。从数据挖掘里来的策略(复杂的因子组合)PBO 高。

统计套利/配对交易:PBO 通常高(40–70%),因为策略空间巨大,多数变种都是噪声。少数真实的 edge(比如特定配对的协整)被噪声淹没。

含义:如果你在跑动量或统计套利,预期 PBO 会高。相应地调整工作流:少跑候选策略,用更强的先验,在你能信它之前要求高得多的原始 Sharpe。

不理 PBO 会发生什么

标准模式是:

  1. Quant 在 50–200 个组合上跑超参搜索。
  2. 他们挑最佳 Sharpe,通常是中位数的 1.5–2.5 倍。
  3. 他们不跑 CSCV 或 PBO。
  4. 他们用小 AUM 部署。
  5. 实盘 Sharpe 在中位数附近,不在最佳附近。
  6. 他们放大 AUM,信了回测。
  7. 实盘表现崩盘。
  8. 他们关掉策略。

第 3 步到第 5 步的 Sharpe 压缩正是 PBO 预测的。第 7 步的崩盘就是你放大一条从来没那么好的策略时会发生的事。我在三家不同的量化机构见过这个模式。变化的是亏损规模,不是失败的结构。PBO 全都能抓到。

诚实的总结

PBO 是量化金融里最让人不舒服的指标。它告诉你你最好的回测比中位数还差的概率。大部分 quant 算都不算。大部分算过的 quant 算了也不理。认真对待它的人,救了自己不去部署那些看着漂亮但会挂的策略。

参考文献:López de Prado, Advances in Financial Machine Learning, §11.6 (Strategy Selection / CSCV), ISBN 978-1-119-48208-6. Bailey, Borwein, López de Prado, Zhu (2017), “The Probability of Backtest Overfitting,” Journal of Computational Finance, 20(4), pp. 39-70. Bailey and López de Prado (2014), “The Deflated Sharpe Ratio.”