回测过拟合概率(PBO):完整推导
PBO 打分的对象是你的挑选动作,不是你的策略。从 CSCV 出发把 logit 排名和 PBO 定义推一遍,顺带说清楚这个数字自己有多噪。
你从 个候选策略中挑出了样本内夏普最高的那个。它在样本外掉到中位数以下的概率是多少?这个概率就是 PBO。
在开始推导之前,先把一句话钉住,因为整篇文章都挂在它上面:PBO 打分的对象是你的挑选动作,不是你的策略。它不回答”这个策略好不好”,它回答”我刚才那个’挑样本内最好的’的操作,有没有比抛硬币强”。这个区分听起来像语义学,但它决定了 PBO 的参考点为什么是 0.5 而不是 0,也决定了为什么一个 PBO = 0.1 的策略仍然可能完全不能用。
本文从 CSCV 推导 PBO:走一遍 logit 变换,写清楚定义,然后拿一份能跑的实现去量一量这个数字自己有多不稳。
PBO 衡量什么
PBO 回答的问题是:“如果我反复运行这个程序——搜索 个策略、选出样本内最好的那一个——被选中的策略有多大比例会掉到这 个策略的样本外中位数以下?”
生产环境的对应物是样本外时期,也就是搜索过程中策略没见过的数据。CSCV 通过反复保留一半数据来模拟这件事,PBO 在所有这些模拟上取平均。
三个锚点:PBO = 0 意味着样本内赢家在样本外始终是最好的;PBO = 0.5 意味着它落在中位数上下的次数一样多,你的挑选动作没提供任何信息;PBO = 0.9 意味着样本内赢家几乎总是掉到中位数以下,你的程序在稳定地挑错。
注意最后一种情况的性质:这不是”策略不好”,这是”你的挑选方式反着来”。同一批策略换个挑法,结果可能完全不同。
CSCV 给出的东西
上一篇文章介绍的 CSCV 算法,输出是一个相对样本外排名的向量:
其中 是 个块上 条对称等质量路径之一。
这里必须把方向约定说死,否则后面所有符号都会反过来。本文用的约定是:把 个策略按样本外表现从好到坏排序,样本内赢家排在第 位( 表示样本外也最好),则
分母用 而不是 ,是为了让 严格落在开区间 内,后面取 logit 时不会跑到无穷。在这个约定下 越小越好,而 就表示样本内赢家掉到了样本外中位数以下。
(Bailey、Borwein、López de Prado 和 Zhu 的原文用的是升序排名,也就是 越大越好,于是他们的 PBO 写成 。两种写法完全等价,只是符号相反。读文献时先确认作者用的是哪一种,不然会把结论读反。)
的经验 CDF 是:
这就是全部的原料。PBO 是从这个 CDF 上读出来的一个数。
Logit 变换
排名 被限制在 内,这对做正态近似不方便。标准做法是取 logit:
变换之后 落在整条实数轴上。在”没有过拟合也没有优势”的原假设下, 的经验分布近似正态、以 0 为中心。
要说清楚的是,logit 不增加也不减少信息——它是单调变换,带 logit 和不带 logit 的 CDF 携带同样的内容。它带来的是两个便利:分布更接近正态,于是可以用一个闭式近似代替离散计数;以及”中位数”这个关键位置从 挪到了 ,对称性看得见了。
PBO 的定义
有了方向约定,定义只需要一行。PBO 是样本内赢家掉到样本外中位数以下的路径占比:
用 logit 写是同一件事:
两者等价,因为 。
注意公式里是 而不是 。 按定义是 的占比,也就是样本内赢家表现好的那一半;PBO 要的是另一半。这个符号很容易写反,写反了会让一个好策略看起来像灾难。
阈值 0.5 不需要从数据里估,它就是样本外中位数的定义位置。有些实现用 而不是严格大于;在 这个取值网格上,只有 为奇数时才可能取到恰好 0.5,差别可以忽略。
为什么 0.5 是参考点
在既没有过拟合也没有优势的世界里,样本内夏普最高的那个策略相当于是从 个里随机抽的。它的样本外名次 在 上均匀分布,于是 在 上均匀,这个网格关于 0.5 对称。落在 0.5 以上和以下的概率各一半,所以
这解释了为什么”PBO = 0.4 算不算好”这种问题需要先想清楚参考点。0.4 不是”40% 概率过拟合”,是”比随机挑略好一点”。
顺着这条线,最常见的误读也就清楚了:PBO 不是”策略过拟合的概率”。它是样本内赢家掉到样本外中位数以下的概率,而这个概率在无信息的情况下天然是 0.5。大于 0.5 是坏消息,小于 0.5 是好消息,等于 0.5 是没消息。
怎么读一个 PBO 数字
| PBO | 解读 |
|---|---|
| < 0.1 | 挑选程序信息量强,样本内赢家在样本外稳定靠前 |
| 0.1 – 0.3 | 中等信息量,是研究阶段升级的常见门槛 |
| 0.3 – 0.5 | 微弱信息量,略好于随机挑 |
| 0.5 – 0.7 | 微弱过拟合,挑选程序略差于随机 |
| 0.7 – 0.9 | 强过拟合,样本内赢家通常是样本外较差的那批 |
| > 0.9 | 极端过拟合,挑选程序在稳定地反向选择 |
多数研究团队把 PBO < 0.3 作为从研究升级到模拟交易的门槛,PBO > 0.5 则视为搜索程序本身已经失效、结果不可用。这个门槛是团队参数而不是自然常数:假阳性代价高的场景(部署成本大的高频策略)会把它压到 0.1,容忍度高的探索性研究可能放到 0.4。决定门槛的应该是”选错一次要付多少钱”,不是习惯。
Logit 正态近似
离散计数出来的 PBO 只能取 的整数倍,路径少时会很粗糙。用 logit 排名的样本均值和样本标准差
可以得到一个平滑估计:
其中 是标准正态 CDF。原假设下 ,代进去得到 ,和离散版本对上。
的量级由 决定,因为策略越多、排名网格越宽。均匀排名下算一下: 时 logit 排名的标准差约 1.11, 时约 1.33, 时约 1.50, 时约 1.64。增长很慢——这是 logit 的对数尺度决定的,也意味着 稍微偏离 0 一点,PBO 就会明显偏离 0.5。
路径数 够大(一般 )时正态近似不错;路径少时它比经验 CDF 更平滑,但也更依赖”分布确实近似正态”这个前提,遇到双峰分布会给出误导性的结果。这时候应该直接看分布,不要看任何单个数字。
一个算到底的例子
取 个策略, 个块,于是路径数是 。
跑完 CSCV,你手上有 12870 个相对样本外排名 。假设经验分布长这样:
- 5000 条路径落在 :样本内赢家在样本外也是第一四分位。
- 2000 条路径落在 :第二四分位。
- 1500 条路径落在 :第三四分位。
- 4370 条路径落在 :第四四分位。
PBO 就是后两档的占比:
0.456 略低于 0.5,提示挑选程序有微弱信息量。但请注意这个分布的形状——两头都很重(5000 条在最好的四分位,4370 条在最差的四分位),中间很空。这是双峰,不是”略好于随机”的平滑分布。单看 0.456 这个数会得出”温和向好”的印象,看分布则会得出”这个挑选动作方差极大”的印象,后者才是真的。这也是为什么下面要专门讲图。
换一份过拟合严重的分布:
- :1000 条
- :800 条
- :2000 条
- :9070 条
0.86,强过拟合:样本内赢家有 86% 的时间掉到样本外中位数以下,而且质量高度集中在最差的四分位。
一份可跑的实现
import numpy as np
from itertools import combinations
from scipy.special import logit as scipy_logit
def cscv_pbo(returns_matrix, k=16):
"""
Compute PBO via CSCV.
Convention: omega is the IS winner's OOS rank counted from the TOP,
scaled to (0, 1). Small omega = good OOS performance.
PBO is therefore the fraction of paths with omega > 0.5.
Parameters
----------
returns_matrix : ndarray of shape (T, N)
Returns for N strategies.
k : int
Number of blocks (must be even).
Returns
-------
pbo : float
Probability of Backtest Overfitting.
omega : ndarray
Relative OOS ranks, one per path.
omega_bar : ndarray
Logit of the relative OOS ranks.
"""
T, N = returns_matrix.shape
block_size = T // k
blocks = [returns_matrix[i * block_size:(i + 1) * block_size] for i in range(k)]
paths = list(combinations(range(k), k // 2))
omega = np.zeros(len(paths))
for s_idx, S in enumerate(paths):
is_data = np.concatenate([blocks[j] for j in S], axis=0)
oos_blocks = [j for j in range(k) if j not in S]
oos_data = np.concatenate([blocks[j] for j in oos_blocks], axis=0)
is_sharpe = is_data.mean(axis=0) / is_data.std(axis=0, ddof=1)
oos_sharpe = oos_data.mean(axis=0) / oos_data.std(axis=0, ddof=1)
is_winner = np.argmax(is_sharpe)
oos_ranking = np.argsort(-oos_sharpe) # descending: position 0 is best
rank = np.where(oos_ranking == is_winner)[0][0]
omega[s_idx] = (rank + 1) / (N + 1) # (0, 1), so logit stays finite
omega_bar = scipy_logit(omega)
pbo = (omega > 0.5).mean()
return pbo, omega, omega_bar
# Demo: 10 strategies, one of them with a real edge
rng = np.random.default_rng(42)
T, N = 2000, 10
daily_vol = 0.01
true_mean = np.zeros(N)
true_mean[0] = 0.0008 # daily mean -> annualized Sharpe of about 1.27
returns = true_mean + rng.normal(0, daily_vol, size=(T, N))
pbo, omega, omega_bar = cscv_pbo(returns, k=8)
print(f"PBO: {pbo:.4f}")
print(f"Median omega: {np.median(omega):.4f}")
这份 demo 用 ,路径数只有 ,跑得快。输出是 PBO = 0.0857、中位数 = 0.0909——那个真有优势的策略被稳定地挑了出来,符合预期。把 true_mean[0] 设回 0(谁都没有优势),PBO 会跳到 0.5 附近。
注意 true_mean[0] = 0.0008 是日均收益而不是夏普:配上 0.01 的日波动率,它对应的年化夏普是 。这两个量在代码里长得很像,混起来会让你以为自己在测一个微弱信号,实际上在测一个很强的信号。
这个数字自己有多噪
上面那句”把 true_mean[0] 设回 0,PBO 会跳到 0.5 附近”值得展开,因为它藏着 PBO 最容易被忽略的性质。
拿 个纯噪声策略、、(70 条路径),换 40 个随机种子各跑一次,得到的 PBO 分布是:均值 0.507,标准差 0.249,最小 0.029,最大 0.929。
均值 0.507 完美落在理论原假设 0.5 上,这说明估计量本身是对的。但标准差 0.249 意味着:一批完全没有任何优势的策略,有相当大的机会给出 PBO = 0.03 这种看起来”证据确凿”的数字,也有相当大的机会给出 0.93 这种看起来”灾难性过拟合”的数字。在这个配置下,单个 PBO 数字几乎不携带可靠信息。
把块数从 提到 (路径数从 70 涨到 12870),标准差降到约 0.17。有帮助,但没有解决——路径之间高度重叠,它们不是 12870 份独立证据。
这带来两条实操结论。第一,块数别省, 应该是起点而不是上限, 只适合写 demo。第二,也是更重要的一条:不要拿 PBO 的小数点后两位做决策。PBO = 0.28 和 PBO = 0.34 在一条阈值线两侧,但它们的差距远小于这个估计量自身的抖动。把 PBO 当成量级读——“接近 0”、“接近 0.5”、“明显大于 0.5”——才是它能支持的精度。
顺带说,这也是”多个 各报一个 PBO”这条建议的真正理由。它不只是稳健性检查,它是在给读者看这个数字的抖动幅度。
看图,不只看数
单个 PBO 数字是摘要,分布才是诊断。常见的画法是把 的经验 CDF 和标准正态 CDF 叠在一起,CSCV 用实线、正态用虚线;或者直接画 对 ,拿对角线当参照。两种画法读法一样:
均匀分布对应 ,曲线贴着对角线,没有过拟合也没有优势。曲线在小 处沉到对角线下方,说明质量往大 跑,过拟合,PBO > 0.5。曲线在小 处抬到对角线上方,说明质量往小 跑,有信息量,PBO < 0.5。
真正需要图才能看出来的是第四种:双峰,曲线在 0 和 1 附近各有一次跳跃,中间很平。这意味着样本内赢家有时是样本外最好的、有时是样本外最差的,方差巨大。前面那个 PBO = 0.456 的例子正是这一类。两个策略可以有完全相同的 PBO 而分布形状完全不同,一个是平滑偏移、一个是双峰,而这两种情况该做的事完全不一样:前者是”优势微弱”,后者是”这个挑选程序不稳定,先别信它”。
研究报告里 PBO 数字和这张图应该一起给。数字方便比较,图防止误读。
PBO 与 DSR
DSR 和 PBO 都在处理多重检验问题,路子不同。
DSR 走闭式的 Gumbel 近似,假设收益率高斯、试验独立,输出的是”报告的夏普能否和原假设下的期望最大值区分开”这个概率。PBO 走 CSCV,一种与模型无关的重采样,不需要分布假设,输出的是”样本内赢家会不会掉到样本外中位数以下”。
取舍是清楚的:DSR 快、解析、可复现,在假设成立时更有区分度;PBO 慢、计算式、结果带抖动,但在假设不成立时更稳健。对高斯收益率加大量试验,两者结论接近。对典型交易策略的厚尾非高斯收益率,PBO 更可靠。 很大(几百个策略)时 PBO 也更实用,因为它不需要处理高维的 Gumbel 近似。
实践上两个都算。结论一致就往下走;不一致时,按更保守的那个(过拟合概率更高的)来报告,同时去查是哪条假设不成立——这通常比争论哪个方法对更有收获。
PBO 不告诉你的事
PBO 是必要诊断,不是充分条件。它的射程之外至少有三件事。
样本内赢家在绝对意义上好不好。 PBO = 0.3 说的是样本内赢家更常落在样本外靠前的位置,是一个相对陈述。如果这 个策略全都是亏钱的,那么”稳定地挑出最不亏的那个”照样给你一个漂亮的 PBO,而样本外夏普依然是负的。PBO 从头到尾只看排名,不看水平。
优势有没有大到值得做。 PBO = 0.1 配样本外夏普 0.3,和 PBO = 0.1 配样本外夏普 1.5,是两回事。前者说明你的挑选程序很可靠地挑出了一个不值得部署的策略。幅度是另一套诊断的事。
策略在生产里成不成立。 PBO 默认样本外时期能代表生产环境,而容量约束、市场状态切换、执行滑点这些东西都不在它的模型里。一个 PBO = 0.1 的策略照样可能因为容量撑不住而在实盘失败。
还有几条方法层面的边界值得记着。PBO 依赖块的划分,不同 会给出不同的数(这一点上一节量化过了)。它假设 个策略彼此可比,如果你的候选里混了跨资产类别、跨信号类型的东西,“排名”这个操作本身的意义就变弱了。它还假设按块重采样能保留时间依赖,遇到长记忆过程这个假设会破。
它在流程里的位置
一个用得上 PBO 的研究流程大致是这样:通过参数搜索、信号组合或机器学习生成 个候选;选出样本内表现最好的那个;跑 CSCV 算 PBO;PBO 低于门槛就进入样本外测试,高于门槛就放弃或回去改搜索。PBO 卡在边缘时,正确的动作不是调门槛,而是改进搜索本身——减少候选数量、加更强的先验、上正则化。
有一点需要反复提醒:PBO 是数据的函数,数据变了它就变。去年 PBO = 0.2 的策略,今年可能变成 0.5,因为新到的一年数据没有确认那个优势。把 PBO 算一次就当定论是最常见的误用;诚实的做法是随着数据积累重算,并且把变化记下来。
它也只是诊断体系里的一格:
| 诊断 | 回答的问题 |
|---|---|
| PSR | 报告夏普能否和基准区分开? |
| Haircut Sharpe | 夏普是否被自相关抬高? |
| DSR | 夏普是否被多重检验抬高? |
| MBL | 回测是否足够长? |
| PBO | 策略选择是否过拟合? |
| CPCV | 交叉验证是否诚实? |
| 容量 | 优势在更大规模下是否保持? |
| 状态 | 优势在不同市场状态下是否保持? |
这些问题彼此不能替代。PBO 过关只说明你的挑选动作有信息量,剩下每一格都得自己过。
顺便一提,PBO 和 、、真实夏普之间有可以预期的方向性关系:候选越多 PBO 越大(虚假赢家的机会更多),历史越长 PBO 越小(样本内外的估计都更准),真实夏普越高 PBO 越小(赢家更容易脱颖而出)。但和 DSR 那边的最小回测长度(MBL)不一样,PBO 这边没有干净的闭式反解,因为它还取决于策略之间的依赖结构。想知道”要多长历史才能把 PBO 压到某个水平”,最靠谱的办法是照你自己的策略池做模拟,而不是套公式。
参考文献
- Bailey, D. H., Borwein, J., López de Prado, M., and Zhu, Q. J. (2017). “The Probability of Backtest Overfitting.” Journal of Computational Finance, 20(4), 39–70。PBO 的原始论文。
- López de Prado, M. (2018). Advances in Financial Machine Learning. Wiley. ISBN 978-1-119-48208-6。第 11 章讲回测过拟合与相关的 Python 实现。
- Harvey, C. R., Liu, Y., and Zhu, H. (2016). “…and the Cross-Section of Expected Returns.” Review of Financial Studies, 29(1), 5–68。因子研究里多重检验偏差的实证证据。