跳到正文

学习路线 04:回测过拟合 — PBO 框架

7步学习路线:从理解为什么回测会撒谎,到测量过拟合概率,到做出 go/no-go 决策。

第 4 篇 · 共 7 篇 约 3 分钟

目标:完成后,你将理解回测过拟合概率(PBO)如何计算,如何解读,以及如何基于此做出决策。

前置知识:熟悉 CPCV(学习路线 02)和多重检验概念。


第 1 步:这是什么?

你优化了一个交易策略。经过数月工作,你找到一组参数,产生漂亮的回测:Sharpe 2.1,最大回撤 8%,月度收益稳定。

然后你上实盘。三个月后,Sharpe 是 0.4。六个月后,是负的。

发生了什么?

你过拟合了。你测试了如此多的参数组合,找到一个在历史数据上恰好看起来很棒的组合——但那个组合不能泛化到未来数据。

PBO(回测过拟合概率) 是这个风险的正式度量。它问:“考虑到你测试了多少组合,你最佳的组合是假阳性的概率是多少?”


第 2 步:为什么这很重要?

不舒服的数学。假设你测试了 1,000 个参数组合。每个是随机游走(没有真实优势)。在 α=0.05,你会期望 50 个显示”显著”正收益。

其中最佳的 Sharpe 可能是 1.8。

PBO 回答:在所有看起来很棒的组合中,有多少比例在样本外表现糟糕?

如果 PBO = 30%,那么 30% 的情况下,一个在回测中看起来惊艳的参数组合在真实交易中表现不佳。这是 1/3 的失败机会。你会乘坐有 1/3 引擎故障概率的飞机吗?


第 3 步:核心机制

PBO 使用 CPCV(组合清仓交叉验证)作为引擎。想法:

  1. 枚举所有 2^k - 1 种将数据划分为 train/test 的方式
  2. 对于每个划分,按训练 Sharpe 和测试 Sharpe 对策略排名
  3. 计数:高训练排名变成低测试排名的频率是多少?

“高训练、低测试”是过拟合的标志。一个只在特定数据分割上看起来好的策略——不稳健。


第 4 步:关键公式

PBO 定义: PBO=1Ncomb∑i=1Ncomb1[Ranktrain(i)≤αtrain⋅Ncomb AND Ranktest(i)>αtest⋅Ncomb]PBO = \frac{1}{N_{comb}} \sum_{i=1}^{N_{comb}} \mathbf{1}\left[Rank_{train}^{(i)} \leq \alpha_{train} \cdot N_{comb} \ AND \ Rank_{test}^{(i)} > \alpha_{test} \cdot N_{comb}\right]

决策阈值:

PBO解读行动
< 5%低过拟合风险谨慎行事
5-25%中等风险需要额外验证
> 25%高风险可能过拟合——拒绝或简化

第 5 步:实现

import numpy as np
from itertools import combinations

def compute_pbo_detailed(returns_matrix, k=6, top_pct=0.10, bottom_pct=0.50):
    """
    Compute PBO with full analysis output.
    """
    n_samples, n_strategies = returns_matrix.shape
    samples_per_fold = n_samples // k

    full_sharpes = np.array([sharpe_ratio(returns_matrix[:, i])
                              for i in range(n_strategies)])

    train_sharpes = []
    test_sharpes = []
    train_ranks = []
    test_ranks = []

    for r in range(1, k + 1):
        for train_folds in combinations(range(k), r):
            train_mask = np.zeros(k, dtype=bool)
            train_mask[list(train_folds)] = True
            test_mask = ~train_mask

            train_samples = np.zeros(n_samples, dtype=bool)
            test_samples = np.zeros(n_samples, dtype=bool)

            for fold_idx in range(k):
                start = fold_idx * samples_per_fold
                end = start + samples_per_fold
                if train_mask[fold_idx]:
                    train_samples[start:end] = True
                if test_mask[fold_idx]:
                    test_samples[start:end] = True

            if train_samples.sum() > 0 and test_samples.sum() > 0:
                for s in range(n_strategies):
                    train_ret = returns_matrix[train_samples, s]
                    test_ret = returns_matrix[test_samples, s]

                    tsr = sharpe_ratio(train_ret)
                    esr = sharpe_ratio(test_ret)

                    train_sharpes.append(tsr)
                    test_sharpes.append(esr)
                    train_ranks.append(np.sum(full_sharpes > full_sharpes[s]))
                    test_ranks.append(np.sum(test_sharpes[-1] > np.array(test_sharpes)))

    train_sharpes = np.array(train_sharpes)
    test_sharpes = np.array(test_sharpes)
    train_ranks = np.array(train_ranks)
    test_ranks = np.array(test_ranks)

    n_comb = len(train_sharpes)
    top_threshold = int(top_pct * n_comb)
    bottom_threshold = int(bottom_pct * n_comb)

    overfitted = (train_ranks < top_threshold) & (test_ranks >= bottom_threshold)
    pbo = np.mean(overfitted)

    train_test_corr = np.corrcoef(train_sharpes, test_sharpes)[0, 1]

    return {
        'pbo': pbo,
        'n_combinations': n_comb,
        'n_overfitted': np.sum(overfitted),
        'train_test_correlation': train_test_corr
    }


def sharpe_ratio(returns, periods_per_year=252):
    if np.std(returns) == 0:
        return 0.0
    return np.mean(returns) / np.std(returns) * np.sqrt(periods_per_year)

第 6 步:如何解读结果

PBO < 5%:策略表现稳健。仅有 5% 看起来很棒的策略在样本外看起来很糟糕。按标准风险管理进行。

PBO 5-25%:中等关注。策略可能过拟合。上实盘前:

  • 尝试更简单的版本(更少参数)
  • 要求更多数据(如果可能)
  • 留出验证集不用于任何优化

PBO > 25%:高过拟合概率。策略有 >1/4 机会是假阳性。拒绝或回到设计阶段。

训练-测试相关性也重要。 如果 train_sharpe 和 test_sharpes 高度相关(例如 > 0.7),策略是稳健的。如果相关性低或为负,策略是脆弱的。


第 7 步:常见陷阱 + 在知识体系中的位置

陷阱:使用的 k 太小。 k=4 给 15 种组合。对于可靠的 PBO 估计太少。使用 k≥6(63+ 组合)。

陷阱:不清仓。 不清仓的 CPCV 给出有偏 PBO(过于乐观低)。总是使用根据你信号校准的 purge 间隙。

陷阱:把 PBO 解释为”策略无价值的概率”。 PBO 是有条件的:假设你测试了许多组合,“看起来很棒的策略实际上是假阳性的概率”。它不告诉你策略有零技能的概率。

在知识体系中的位置:

  • 上实盘前任何优化策略的最终验证
  • 比较策略稳健性(更低 PBO = 更稳健)
  • 向投资者传达过拟合风险

PBO 是验证管道的一部分:purged CV → CPCV → PBO → DSR。每个步骤增加严谨性。


下一步

准备好下一级了吗?→ 学习路线 05:概率化回测

想要百科参考卡片?→ 回测过拟合:PBO(百科词条)