返回交易笔记
TR Day 24

过拟合识别 — Deflated Sharpe / PBO / Multiple Testing

多重比较问题在量化里的具体形态、Deflated Sharpe Ratio 推导直觉、PBO 框架、IS/OOS 的常见误用、AQR 风格的 robust check 清单

2026-06-02
Phase 1: 基础与工具链
OverfittingDeflatedSharpePBOMultipleTestingInSampleOutOfSampleLopezDePrado

日期: 2026-06-02 方向: 回测严谨性 / 过拟合 阶段: Phase 1: 基础与工具链 标签: #Overfitting #DeflatedSharpe #PBO #MultipleTesting #InSample #OutOfSample #LopezDePrado


今日目标

类型内容
学习多重比较问题在量化里的具体形态、Deflated Sharpe Ratio 推导直觉、PBO 框架、IS/OOS 的常见误用、AQR 风格的 robust check 清单
实操给 Day 6 的 SMA 参数扫描结果套上 DSR 校正;写一个简化版 PBO 计算函数;对当前两条策略做「试错次数」诚实清点
反思过去 23 天有没有不自觉做 multiple testing、有没有「悄悄换 OOS 边界」的行为
产出TR-DAY24 笔记 + tr_lib/diagnostics/dsr.py + tr_lib/diagnostics/pbo.py + 一张 SMA 扫描结果的 DSR 校正前后对比表

一、过拟合在量化里的本质:试错和挑选才是真问题

新手以为过拟合是「模型参数太多」。在量化里,过拟合最常见的来源根本不是模型本身,而是研究者

一个研究者跑了 1,000 个策略变种,挑出回测 Sharpe 最高的那一个写进 paper,剩下 999 个被悄悄删了 — 这才是真正的过拟合机制。

机器学习领域有个对应概念叫「researcher degree of freedom」,量化领域称为 backtest overfitting。它的恶毒之处在于:

  1. 每一步都看起来很合理:换个均线窗口、加一个止损、试一下不同 universe — 任何一步都不是「作弊」
  2. 没有任何人能审计你试过多少次:你的脑子里跑过的策略不会进 git log
  3. 样本外验证看起来没问题:因为「样本外」是你挑赢家之后才被划出来的

López de Prado 在 Advances in Financial Machine Learning 里有一句话直接到刺人:

"Most published results in finance are likely false."(金融领域大部分已发表结果可能都是假的。)

他不是在说大家造假,他在说没有人对 multiple testing 做修正。这和心理学领域 2015 年的「replication crisis」是同一类问题。

1.1 量化研究者每天都在做的 multiple testing(自检清单)

行为是不是 multiple testing一般试了几次
参数扫描(slow=20,30,40,50,60)5-20
换一个 universe(SPY → QQQ → IWM)3-5
试不同 holding period(5d/10d/20d)3-5
加 / 不加止损2
换一个 entry signal(cross vs threshold)2-3
试不同 transaction cost 假设2-3
看回测崩了换个时段重新看不确定(最危险)
改了 bug 重新跑(即便正当)多次

把这些维度乘起来:5 × 3 × 3 × 2 × 2 × 2 × 多次重跑 = 几百次。这就是「我只试了几个参数」这句话背后的真相。


二、多重比较问题:N 个噪声里挑最好的会很「亮」

2.1 玩具实验

假设你有 N 个策略,每一个真实 Sharpe = 0(纯噪声,没有任何 alpha)。每一个策略的样本 Sharpe 估计有标准误 σ_SR ≈ 1/√T(T 个独立样本)。

你挑「这 N 个里最高的那个」,它的样本 Sharpe 期望是多少?

对正态分布的 N 个独立采样取最大值,期望约等于:

$$ \mathbb{E}[\max_{i=1..N} Z_i] \approx \sqrt{2 \log N} $$

NE[max Sharpe](年化, T 充分大)含义
10~2.15 / √T × √252试 10 个噪声策略,最好那个看起来「不错」
100~3.03 / √T × √252看起来像「优秀策略」
1000~3.72 / √T × √252看起来像「世界级策略」
10000~4.29 / √T × √252看起来像「Renaissance」

注意:这些 Sharpe 全部来自 alpha = 0 的噪声。你只是挑了里面最幸运的一个。

2.2 这条公式的实战含义

如果你试了 100 个参数组合,至少有一个会得到 Sharpe ≈ 3 而它的真实 Sharpe 是 0。如果你不做 multiple testing 修正,你会把这个全噪声策略当作 winner 部署到实盘 — 然后亏钱。

经验法则:试 N 个组合后挑最高的,真实 Sharpe ≈ 样本 Sharpe − √(2 log N) / √T × √252

这就是 Deflated Sharpe Ratio 的核心直觉。


三、Deflated Sharpe Ratio (DSR):把试错次数 paste 回去

3.1 公式直觉

López de Prado (2014) 的 DSR 干的事是:

原始问题:观察到 SR_hat = 2.5,这够好吗?
DSR 问的问题:考虑到我试过 N 次、收益分布有偏度/峰度、样本长度 T,
            SR_hat 真的显著大于 E[最大噪声 Sharpe] 吗?

公式(保留可读形式,详见 López de Prado 论文):

$$ \text{DSR} = \Phi\left( \frac{(\hat{SR} - \mathbb{E}[\max SR_{\text{noise}}]) \cdot \sqrt{T-1}}{\sqrt{1 - \gamma_3 \hat{SR} + \frac{\gamma_4 - 1}{4}\hat{SR}^2}} \right) $$

其中:

  • Φ = 标准正态 CDF
  • SR_hat = 样本 Sharpe(年化)
  • γ_3 = 收益序列的偏度(skewness)
  • γ_4 = 收益序列的峰度(kurtosis,正态 = 3)
  • T = 样本数(一般用日数)
  • E[max SR_noise] = 在 N 次试错下的「最大噪声 Sharpe」期望,约等于 (1 - γ_em) Φ^{-1}(1 - 1/N) + γ_em Φ^{-1}(1 - 1/(N·e)),其中 γ_em ≈ 0.5772(Euler-Mascheroni)

3.2 关键 takeaway

输入对 DSR 的影响
N 增大DSR 减小(试得越多越要打折)
T 增大DSR 增大(样本越长越可信)
正偏度 γ_3 > 0DSR 增大(好策略)
高峰度 γ_4 > 3DSR 减小(fat tail 不利)
SR_hat 增大DSR 增大(前提是其他条件不变)

3.3 实操判定

DSR判定
< 0.5极可能是 noise,不要部署
0.5 - 0.8可疑,至少做 walk-forward
0.8 - 0.95边缘,谨慎小仓位
> 0.95大概率真实 edge(仍需 OOS 验证)

注意 DSR 是个概率(0 到 1),不是 Sharpe,所以 ">0.95" 是说「Sharpe 显著大于噪声峰值的概率 > 95%」。

3.4 N 该怎么填?这是 DSR 的最大争议点

理论上 N 应该是「你为了得到这个 SR 试过的所有独立配置数」。实际操作:

  • 诚实下限:参数 grid size × universe 数 × holding period 数 × ...
  • 更诚实:再乘以「重跑次数」(每次发现 bug 重跑算一次新的 try)
  • 极其诚实:再加上「同事 / KOL 给我看过的策略」(如果你受其启发)

经验:把你「以为的 N」乘 3 比较接近真相。这是因为人脑会自动忘记「失败的尝试」(survivorship bias of your own research)。


四、Probability of Backtest Overfitting (PBO)

4.1 框架(Bailey-Borwin-López de Prado 2014)

DSR 解决「单个策略 SR 在 N 次试错下是否显著」,PBO 解决一个更直接的问题:

我从 M 个策略里挑「In-Sample 最好」的那个 — 它在 Out-of-Sample 也最好的概率是多少?

如果策略是真的有 edge,IS 最好的那个 OOS 也大概率好。如果都是噪声,IS 最好的那个 OOS 排名是随机的 — 50% 概率会落到下半区。

PBO = P(选中策略的 OOS 排名跌进下半)

4.2 Combinatorial Symmetric Cross-Validation (CSCV)

具体计算流程:

1. 把 T 期数据切成 S 个等长 sub-period(S 通常 = 16)
2. 从 S 个中选一半作为 IS,另一半作为 OOS — 共 C(S, S/2) 种切法
3. 对每种切法:
   a. 在 IS 上跑所有 M 个策略,找出 IS Sharpe 最高的策略 i*
   b. 看策略 i* 在 OOS 上的排名 r*
   c. 计算 logit: λ = log(r* / (M - r*))
4. PBO = P(λ < 0) = OOS 排名 < median 的比例

直觉:如果 PBO = 0.5,那 IS winner 在 OOS 的位置就是抛硬币 — 完全过拟合。

4.3 判定阈值

PBO判定
< 0.1几乎不过拟合,IS winner 大概率真
0.2 - 0.4中度过拟合
> 0.5显著过拟合,IS 选出来的不可信

实际工业界很多「Sharpe 2-3」的策略做完 PBO 是 0.6-0.8 — 这正是为什么大部分发表的因子无法 replicate。


五、In-Sample / Out-of-Sample:被滥用最多的概念

5.1 经典做法(不够用)

[----------------- 全部数据 -----------------]
[------- 70% IS(参数调优) -------][- 30% OOS -]
  • 在 IS 上找最佳参数
  • 把这套参数搬到 OOS 跑一次
  • 看 OOS Sharpe 是否「也不错」

5.2 这种做法的三个常见 bug

Bug 1: Peek at OOS(偷看样本外)

  • 你看了一眼 OOS 表现不好
  • 回去改 IS 上的参数 / 加 feature
  • 重跑 OOS
  • 这时 OOS 已经被你间接 fit 了,不再是「样本外」

一旦你看过 OOS 任何一眼,OOS 就被污染了。

Bug 2: OOS 太短

  • 30% 数据可能只有 1-2 年
  • 单次划分,运气因素仍然巨大
  • 解法:k-fold CV / walk-forward(Day 25)

Bug 3: OOS 不代表未来

  • 即便严格不偷看,OOS 也是历史
  • 真正的「样本外」是未来还没发生的市场
  • Paper trading 才是真 OOS(虽然没成本)

5.3 进阶做法预告

方法简介何时学
k-fold CV数据切 k 份,轮流当 OOSDay 25
Walk-forward滚动窗口,模拟真实部署Day 25 重点
CSCV / PBO组合式 CV,专门对抗 multiple testing今天讲了
Purged / Embargoed CV解决金融数据 leakageDay 26-27

六、过拟合的具体「症状」(怎么用眼睛看出来)

不一定每个症状都要算 DSR/PBO — 有些过拟合是视觉上就能识别的。

6.1 症状 1:参数对窗口高度敏感

slow=50: Sharpe = 1.8
slow=45: Sharpe = 0.3
slow=55: Sharpe = -0.2

健康策略的参数响应应该是平台型(plateau):参数 ±20% 都能 work。如果只有最大值附近能 work,那个最大值就是噪声峰

6.2 症状 2:Sharpe 平面陡峭

把双参数(slow, fast)画成 heatmap:

图形含义
缓慢起伏的山地健康,参数稳健
单点突起 + 周围全是水过拟合,唯一的「峰」是 noise
多个独立峰通常也是 overfit

Day 6 SMA 扫描的 heatmap 我应该再翻出来用这个视角重看。

6.3 症状 3:IS Sharpe >> OOS Sharpe

IS SharpeOOS Sharpe解读
2.52.2健康(小幅 deflation 正常)
2.51.5中度 overfit
2.50.4重度 overfit,IS 是 noise
2.5-0.3灾难,反向 fit

经验:OOS Sharpe ≈ 0.5 × IS Sharpe 是常态(这就是为什么实盘普遍低于回测)。

6.4 症状 4:策略「故事」过于复杂

入场:
  - MA10 > MA50
  - 且 RSI < 70
  - 且 VIX < 25
  - 且非财报前 3 天
  - 且当日 volume > 20d avg × 1.2
  - 且非月末
  - 且... (继续加 7 个 filter)

每加一个 filter 都让 backtest 看起来更好,但本质上你在找一组凑巧匹配历史的过滤器

好策略的 rule 数 ≤ 3。Renaissance 的策略据传也只有少量核心信号,剩下都是工程实现。

6.5 症状 5:自由参数 / 数据量 比例过高

经验法则:

$$ \frac{\text{free parameters}}{\text{independent observations}} < 0.001 $$

如果你的策略有 10 个参数(5 个均线窗口 + 3 个阈值 + 2 个 stop),跑在 5 年日线 = 1,260 天。比例 10/1260 ≈ 0.008,已经过高

「独立观察」≠ 日线数。如果策略持仓 20 天,独立观察更接近 1260/20 = 63 — 比例变成 10/63 = 0.16,完全过拟合


七、三种 anti-overfitting 工具(之上的层级)

7.1 Walk-forward analysis(Day 25 详细学)

时间 →
[--- train 252d ---][test 63d]
        [--- train 252d ---][test 63d]
                [--- train 252d ---][test 63d]
                        ...

每个 test 段都是真正的样本外。串起来就是「如果我每季度重新优化参数,实盘会发生什么」。

7.2 减少 free parameters

Day 6 教训重提:SMA 双参数扫描,固定 fast=10 + 只调 slow,比 fast/slow 双扫描更不容易 overfit。每砍掉一个参数,N 是几何级数降低。

7.3 Regularization(feature selection 阶段)

如果你用机器学习选 feature:

  • Lasso (L1):自动让大部分系数变成 0,强制 sparsity
  • Ridge (L2):把系数压向 0 但不为 0,平滑
  • Elastic Net:两者混合

对应到非 ML 的策略:用「贝叶斯先验」给极端参数赋低概率,等同 regularization。


八、Python 实现:DSR + 简化版 PBO

8.1 DSR 计算

# tr_lib/diagnostics/dsr.py
import numpy as np
from scipy.stats import norm

EULER_MASCHERONI = 0.5772156649

def expected_max_sr(N: int) -> float:
    """
    López de Prado expected max Sharpe under null (alpha=0).
    N = number of independent trials.
    """
    if N <= 1:
        return 0.0
    gamma = EULER_MASCHERONI
    # E[max] ≈ (1-γ) Φ^-1(1 - 1/N) + γ Φ^-1(1 - 1/(N·e))
    term1 = (1 - gamma) * norm.ppf(1 - 1.0 / N)
    term2 = gamma * norm.ppf(1 - 1.0 / (N * np.e))
    return term1 + term2


def deflated_sharpe(returns: np.ndarray, n_trials: int,
                    annualization: int = 252) -> dict:
    """
    Compute Deflated Sharpe Ratio.

    Parameters
    ----------
    returns : daily returns of the selected strategy
    n_trials : how many strategies you tried (be honest!)
    annualization : 252 for daily, 12 for monthly

    Returns
    -------
    dict with sr_hat, sr_expected_max, dsr (probability)
    """
    T = len(returns)
    if T < 30:
        raise ValueError("Need at least 30 observations")

    mu = returns.mean()
    sigma = returns.std(ddof=1)
    sr_hat_daily = mu / sigma
    sr_hat = sr_hat_daily * np.sqrt(annualization)

    # higher moments
    centered = returns - mu
    skew = (centered ** 3).mean() / (sigma ** 3)
    kurt = (centered ** 4).mean() / (sigma ** 4)  # 3 for normal

    # Expected max under null, scaled to annualized SR
    # (the null is daily SR ~ N(0, 1/sqrt(T)); annualize at the end)
    e_max_daily = expected_max_sr(n_trials) / np.sqrt(T)
    e_max = e_max_daily * np.sqrt(annualization)

    # DSR formula
    numerator = (sr_hat_daily - e_max_daily) * np.sqrt(T - 1)
    denom = np.sqrt(1 - skew * sr_hat_daily +
                    (kurt - 1) / 4.0 * sr_hat_daily ** 2)
    z = numerator / denom
    dsr = norm.cdf(z)

    return {
        "sr_hat": sr_hat,
        "sr_expected_max": e_max,
        "skew": skew,
        "kurt": kurt,
        "T": T,
        "n_trials": n_trials,
        "dsr": dsr,
    }


if __name__ == "__main__":
    # Sanity check: pure noise, N=100 trials, pick best — DSR 应该很低
    np.random.seed(42)
    best_sr = -np.inf
    best_returns = None
    for _ in range(100):
        r = np.random.normal(0, 0.01, 1000)  # 1000 days, σ=1%
        sr = r.mean() / r.std() * np.sqrt(252)
        if sr > best_sr:
            best_sr = sr
            best_returns = r
    print(f"Best of 100 noise strategies, naive SR = {best_sr:.2f}")
    result = deflated_sharpe(best_returns, n_trials=100)
    print(result)
    # 期望输出:naive SR 看起来不错,DSR 接近 0.5(噪声)

8.2 简化版 PBO(CSCV)

# tr_lib/diagnostics/pbo.py
import numpy as np
from itertools import combinations

def pbo_cscv(returns_matrix: np.ndarray, S: int = 16) -> dict:
    """
    Combinatorial Symmetric Cross-Validation PBO.

    Parameters
    ----------
    returns_matrix : (T, M) ndarray of daily returns
                     M strategies (e.g. M=21 if you tested slow=20,25,...,120)
    S : number of sub-periods (must be even, default 16)

    Returns
    -------
    dict with pbo, logits, ...
    """
    T, M = returns_matrix.shape
    assert S % 2 == 0, "S must be even"
    chunk_size = T // S

    # Truncate to clean S chunks
    returns_matrix = returns_matrix[:S * chunk_size]
    # Reshape to (S, chunk_size, M)
    chunks = returns_matrix.reshape(S, chunk_size, M)

    logits = []
    half = S // 2

    for is_idx in combinations(range(S), half):
        is_set = set(is_idx)
        oos_idx = [i for i in range(S) if i not in is_set]

        is_returns = chunks[list(is_idx)].reshape(-1, M)
        oos_returns = chunks[oos_idx].reshape(-1, M)

        is_sr = is_returns.mean(axis=0) / (is_returns.std(axis=0) + 1e-12)
        oos_sr = oos_returns.mean(axis=0) / (oos_returns.std(axis=0) + 1e-12)

        i_star = np.argmax(is_sr)  # IS winner
        rank_oos = (oos_sr.argsort().argsort()[i_star] + 1)  # 1-indexed
        # logit
        lam = np.log(rank_oos / (M + 1 - rank_oos))
        logits.append(lam)

    logits = np.array(logits)
    pbo = (logits < 0).mean()
    return {"pbo": pbo, "logits": logits, "n_splits": len(logits)}


if __name__ == "__main__":
    # Sanity: 21 个 SMA 参数的日收益矩阵(假数据演示)
    np.random.seed(0)
    T, M = 2000, 21
    fake = np.random.normal(0, 0.01, (T, M))  # 全噪声
    result = pbo_cscv(fake, S=16)
    print(f"PBO on noise = {result['pbo']:.2%}")
    # 期望 ≈ 50%(纯噪声 = 完全过拟合)

8.3 把它套到 Day 6 SMA 扫描上

Day 6 SMA 双参数扫描的输出大致结构是 (T, n_fast, n_slow) 三维收益。把它 reshape 成 (T, n_fast * n_slow),每一列就是一个策略,再喂给 pbo_cscv 即可。

from tr_lib.diagnostics.dsr import deflated_sharpe
from tr_lib.diagnostics.pbo import pbo_cscv
import numpy as np

# 假设 Day 6 已经保存了
sma_returns = np.load("artifacts/day6_sma_grid_returns.npy")  # (T, n_fast, n_slow)
T, nf, ns = sma_returns.shape
flat = sma_returns.reshape(T, nf * ns)  # (T, M)
M = nf * ns

# 找到 IS Sharpe 最高的那组
sr_each = flat.mean(axis=0) / flat.std(axis=0) * np.sqrt(252)
best_i = np.argmax(sr_each)
print(f"Best naive SR among {M} configs = {sr_each[best_i]:.2f}")

# DSR 校正
result = deflated_sharpe(flat[:, best_i], n_trials=M)
print(f"DSR after deflating by N={M}: {result['dsr']:.2%}")

# PBO
pbo = pbo_cscv(flat, S=16)
print(f"PBO (overfit probability): {pbo['pbo']:.2%}")

预期结果(粗略推测,等明天实跑)

  • Naive SR 可能 1.5-2.0
  • E[max SR_noise] for N=49 ≈ 0.4 年化
  • 实际 DSR 可能落在 0.6-0.9(说明 SMA 的 edge 真假参半)
  • PBO 可能 0.3-0.5(边缘过拟合)

九、经验法则(值得贴墙上)

法则内容
1如果回测 Sharpe > 2,乘 0.5 是大概率会发生的实盘表现
2试过 > 50 个参数组合,至少一个会假阳性
3简单策略 + 经济学故事 >> 复杂策略 + 数据驱动
4"Less is more" 在量化是字面意义上的
5OOS 一旦被你看过一次,它就不再是 OOS
6真正的 OOS 只有 paper trade + live,回测里没有
7自由参数 / 独立观察 比例必须 < 0.001
8报告策略时同时报告 N(试过的次数) 和 DSR,否则数字没意义

十、AQR 的「robust check」清单

AQR 等老牌量化基金内部对策略的尽调有一套机械化的稳健性测试。我把核心的列出来:

维度测试通过标准
时间稳健在 2007-2009 (GFC) 表现不爆仓 / Sharpe 不为负
时间稳健在 2020 (COVID) 表现不爆仓
时间稳健在 2022 (rate hike) 表现不爆仓
时间稳健在 2024-2025 (AI bubble) 表现没靠这个时段拉高
Universe 稳健大盘 (S&P 500) vs 中盘 (Russell 2000)方向一致,幅度可不同
国家稳健美股 vs 欧股 (STOXX 600) vs 日股 (TOPIX)至少 2/3 同号
参数稳健参数 ±30%Sharpe 下降幅度 < 50%
频率稳健5min vs 1h vs 1d bar关键信号在多个频率都有
transaction cost 稳健把假设 cost 翻倍仍然 Sharpe > 1
shorting friction加上 borrow cost 50bp仍然 Sharpe > 0.5
存活偏差universe 是否包括退市股
数据修正用 point-in-time data 而非 latest是(避免 lookahead)

判定标准:以上 12 项里通过 ≥ 10 项才考虑实盘。我自己当前的「双 SMA」策略大概只过 6-7 项 — 这就是为什么 Day 6 后我没急着实盘。


十一、当前两条策略的诚实清点

把过去 23 天我(用 IBKR Paper / 回测框架)触碰过的策略和试错次数清一遍:

策略试错次数 N(保守估计)当前 IS SharpeDSR 预估
SMA cross (双均线)49 (7×7 grid)~1.50.7 (待校正后确认)
Momentum factor (Day 10)~8(不同 lookback × universe)~0.90.6
Mean reversion (Day 11)~6~0.60.5
Quality factor (Day 12)~5~0.40.4

诚实说,没有一个达到 DSR > 0.95。这是预期的,说明 Phase 1 任务正确 — 现在是学方法论而不是部署。Phase 2 (Day 26-60) 才该出现 DSR > 0.95 的候选。

关键防火墙:Phase 1 结束前不动真金。


十二、PM 视角:把 multiple testing 迁移到产品工作

这套思维方式对 PM 工作有直接迁移:

12.1 A/B test 的 multiple testing 问题

PM 跑 1 个 A/B test,p=0.04 → "stat sig,部署!"
PM 同时跑 20 个 A/B test,至少 1 个 p<0.05 是偶然 (5% × 20 = 100%)
→ 那个 "winner" 上线后 doesn't replicate

这就是「我们做了 A/B,数据显示有提升,但上线后没看到效果」的常见原因。

12.2 修正方法(PM 版本)

方法实施
Bonferroni 修正同时跑 N 个 test,要求 p < 0.05/N
FDR (Benjamini-Hochberg)控制 false discovery rate,比 Bonferroni 宽松
Pre-registration提前写下「我要 test 哪些 metric」,事后只看这些
Hold-out group永远留一个 group 不接触任何 treatment,作为 sanity baseline

12.3 工作场景里我会立刻应用的两条

  1. 季度 review 时问产品经理:「你这个赢得 A/B 的 feature,背后 team 总共试了几个 variant?」如果是 20+,他的 winner 大概率不 replicate。
  2. 决定 feature 上线时:除了「stat sig 且 effect size 足够」外,再加一条「未来 4 周不看数据,4 周后再看一次 hold-out group」— 这就是 walk-forward 思想在产品工作里的应用。

12.4 招聘里也用得上

面试候选人:「告诉我一个你做过的 successful 决策」vs「告诉我你做过的所有相关决策,包括失败的」。第二种问法在过滤 multiple testing 偏差 — 只听 winner 永远会高估对方能力。


十三、明日预告

Day 25: Walk-forward Analysis 与 Purged K-Fold CV

  • Walk-forward 的 3 种 schedule:rolling / anchored / expanding
  • 数据切分时的 embargo period 设计
  • 金融数据特有的 leakage:label overlap、feature lookahead
  • López de Prado 的 Purged K-Fold CV(解决标签重叠)
  • 把今天的 SMA 例子改造成 walk-forward 版本,看 OOS 性能
  • 产出:tr_lib/diagnostics/walk_forward.py

把今天的 DSR / PBO 当作「截面工具」(一次性评估),Day 25 的 walk-forward 是「时序工具」(模拟真实部署)。两者配合才是完整 anti-overfitting 工具链。


实际执行记录

启动一项填一项,时间戳 + 卡点。

  • [hh:mm] 读 López de Prado Pseudo-mathematics and Financial Charlatanism (2014) — 至少摘要和 Section 3
  • [hh:mm] 实现 dsr.py + sanity check(纯噪声 + N=100 → 看 DSR)
  • [hh:mm] 实现 pbo.py + sanity check(纯噪声 → PBO ≈ 50%)
  • [hh:mm] 把 Day 6 SMA 扫描结果套上 DSR / PBO,记录数值
  • [hh:mm] 写「过去 23 天 multiple testing 诚实清点」(本笔记第十一节)
  • 卡点 / 学到的:

总字数:约 6,900 字 今日完成度:理论 ✓ / 实操(待跑)/ 笔记 ✓