Lesson 2.5.4 · 70 min

Deflated Sharpe Ratio and Probability of Backtest Overfitting (PBO)

A Sharpe of 1.5 does not mean the same if it is the only one tried as if it is the best of a thousand. This lesson codes Bailey and López de Prado's three tools for discounting luck: the probabilistic Sharpe, the Deflated Sharpe and the probability of backtest overfitting, and tests them where the answer is known.

Fig. — Deflated Sharpe Ratio by the number of declared trials: the best strategy with an edge (e0518) and the best noise (e0598) of 1,000 synthetic strategies
Level 2 lessons
  1. 2.1 · Statistics and econometrics for trading

    1. 2.1.1 Hypothesis tests, p-values, bootstrap and regression with robust standard errors
    2. 2.1.2 Stationarity: ADF, KPSS, differencing and fractional differencing
    3. 2.1.3 ARMA and ARIMA models: autocorrelation (ACF) and partial autocorrelation (PACF)
    4. 2.1.4 Volatility models: ARCH, GARCH, EGARCH and GJR with the arch library
    5. 2.1.5 Cointegration: Engle-Granger, Johansen and the half-life of mean reversion
    6. 2.1.6 Kalman filter for dynamic hedge ratios
    7. 2.1.7 Regime detection: hidden Markov models and structural breaks
    8. 2.1.8 The multiple testing problem: Bonferroni, FDR and the “factor zoo”
  2. 2.2 · Anatomy of a trading system

    1. 2.2.1 Edge hypotheses: risk, behavior, structure and information
    2. 2.2.2 Universe, signal, filter, entry, exit, sizing and risk management
    3. 2.2.3 Frequencies: intraday, swing and position trading, and the capacity of a strategy
    4. 2.2.4 Research journal and experiment tracking against data snooping
  3. 2.3 · Strategy families

    1. 2.3.1 Trend following and momentum
    2. 2.3.2 Mean reversion: RSI and Bollinger, intraday reversal and gaps
    3. 2.3.3 Pairs trading and statistical arbitrage: distance, cointegration and Kalman
    4. 2.3.4 Carry in currencies, futures and bonds; seasonality and calendar effects
    5. 2.3.5 Factors (value, quality, low volatility, size) and event strategies
    6. 2.3.6 Introduction to market making: inventory, spread and adverse selection (Avellaneda-Stoikov)
    7. 2.3.7 Crypto-specific strategies: momentum, funding arbitrage and basis
  4. 2.4 · Backtesting: engines and paradigms

    1. 2.4.1 Vectorized backtest from scratch: matrices, lag, costs and limits
    2. 2.4.2 Event-driven backtest from scratch: loop, portfolio, orders and fills
    3. 2.4.3 vectorbt: massive parameter sweeps and portfolio simulation
    4. 2.4.4 backtesting.py and backtrader: two teaching engines and their status
    5. 2.4.5 NautilusTrader: architecture, data, strategies and the same code in backtest and live
    6. 2.4.6 LEAN/QuantConnect and Zipline Reloaded; bt for asset allocation
    7. 2.4.7 Cost modeling: commissions, bid-ask spread, impact, financing and securities borrowing
    8. 2.4.8 Fill modeling: limit orders, queue, partial fills, gaps and stops
  5. 2.5 · Biases and validation

    1. 2.5.1 Backtest biases: look-ahead, survivorship, selection and narrative
    2. 2.5.2 In-sample and out-of-sample: anchored and rolling walk-forward
    3. 2.5.3 Combinatorial purged cross-validation for strategies
    4. 2.5.4 Deflated Sharpe Ratio and Probability of Backtest Overfitting (PBO)
    5. 2.5.5 Monte Carlo: trade resampling, block bootstrap and signal permutations
    6. 2.5.6 Parameter sensitivity: plateaus versus peaks, robustness across assets and periods
    7. 2.5.7 Synthetic data for stress testing: GBM, GARCH and bootstrapping
    8. 2.5.8 “Honest backtest” checklist before moving to paper trading
  6. 2.6 · Performance metrics

    1. 2.6.1 CAGR, Sharpe, Sortino, Calmar, Omega, drawdown and Ulcer Index
    2. 2.6.2 Win rate, payoff, expectancy, alpha, beta, information ratio, turnover and capacity
    3. 2.6.3 Reports with QuantStats or pyfolio-reloaded and custom tearsheets

The Sharpe of the best of N

Lesson 2.5.1 showed the problem with numbers: among 1,000 strategies with no edge, the maximum Sharpe is around 2 even though none of them is worth anything. Lesson 2.5.2 saw the consequence in a walk-forward and 2.5.3, in the distribution of paths of a cross-validation. This lesson gives the tools to correct a Sharpe for luck, at two levels:

  • For one strategy, how much evidence does its Sharpe give, given the length of the series and the shape of its distribution? That is the Probabilistic Sharpe Ratio (PSR).
  • For the best of N, does it beat what you would expect from the best of N trials with no edge? That is the Deflated Sharpe Ratio (DSR). And, if you have the full matrix of trials, how often does the process of choosing the best one end up choosing one worse than the median out-of-sample? That is the probability of backtest overfitting (PBO).
Measured SharpePSR: against 0DSR: against maxN trialsT x N matrixPBO via CSCVDecision
Mermaid source
flowchart TB
  S[Measured Sharpe] --> P[PSR: against 0]
  P --> D[DSR: against max]
  N[N trials] --> D
  M[T x N matrix] --> B[PBO via CSCV]
  D --> X[Decision]
  B --> X

All three are by David Bailey and Marcos López de Prado (with Borwein and Zhu for the PBO), and the formulas in this lesson follow their papers, linked in the resources.

The probabilistic Sharpe (PSR)

The Sharpe estimated with T observations has an error. With normal and independent returns, its standard error is approximately (1+SR^2/2)/T\sqrt{(1 + \widehat{SR}^2/2)/T} in per-period units (Lo, 2002). With fat tails and skewness it is larger: a strategy that makes a little almost always and loses a lot every now and then (negative skewness, high kurtosis) can have a high Sharpe for years without that saying much. The PSR measures the probability that the true Sharpe exceeds a threshold SR∗SR^*, with γ^3\widehat{\gamma}_3 the skewness and γ^4\widehat{\gamma}_4 the kurtosis (3 for the normal) of the returns:

PSR^(SR∗)=Φ((SR^−SR∗)T−11−γ^3 SR^+γ^4−14 SR^2)\widehat{PSR}(SR^*) = \Phi\left(\frac{(\widehat{SR} - SR^*)\sqrt{T-1}}{\sqrt{1 - \widehat{\gamma}_3\,\widehat{SR} + \frac{\widehat{\gamma}_4 - 1}{4}\,\widehat{SR}^2}}\right)

All the Sharpes in the formula are at the frequency of the data (daily here), not annualized.

IN [01]python
import numpy as np
import pandas as pd
from scipy.stats import norm

est = pd.read_parquet("ruido2_estrategias.parquet").pivot(
    index="fecha", columns="estrategia", values="rendimiento")
verdad = pd.read_csv("ruido2_verdad.csv", index_col="estrategia")

def psr(sr, sr_umbral, t, asim, curt):
    """Probabilistic Sharpe Ratio. Per-period Sharpes; `curt` is the
    kurtosis (3 for the normal), not the excess."""
    den = np.sqrt(1 - asim * sr + (curt - 1) / 4 * sr**2)
    z = (sr - sr_umbral) * np.sqrt(t - 1) / den
    if isinstance(z, pd.Series):  # keeps the names
        return pd.Series(norm.cdf(z), index=z.index)
    return norm.cdf(z)

T = len(est)
sr = est.mean() / est.std()           # daily Sharpe
asim, curt = est.skew(), est.kurt() + 3
psr0 = psr(sr, 0, T, asim, curt)

pasan = psr0 > 0.95
reales = verdad["sharpe_real"] > 0
print(f"T = {T} sessions; PSR(0) > 0.95: {pasan.sum()} strategies, "
      f"{(pasan & reales).sum()} with a real edge")
OUT [01]The PSR, from scratch
T = 1003 sessions; PSR(0) > 0.95: 57 strategies, 4 with a real edge

Tested one by one against a Sharpe of 0, more than fifty strategies seem to have an edge with 95% probability, and only four have one. That is to be expected: with 1,000 tests at 5%, about 50 false positives (lesson 2.1.8 covers multiple testing corrections). The PSR is not the problem: it is answering the question for an isolated strategy, and here there are a thousand.

IN [02]python
ruido = reales.index[~reales.to_numpy()]
mejores_ruido = sr[ruido].nlargest(2).index
elegidas = list(reales.index[reales.to_numpy()]) + list(mejores_ruido)
tabla = pd.DataFrame({
    "annual_sharpe": sr[elegidas] * np.sqrt(252),
    "skew": asim[elegidas], "kurtosis": curt[elegidas],
    "PSR(0)": psr0[elegidas],
    "real": verdad.loc[elegidas, "sharpe_real"]})
tabla.round(3)
OUT [02]The five with an edge and the two best of the noise
            annual_sharpe   skew  kurtosis  PSR(0)  real
estrategia
e0080               0.951  0.234     4.657   0.972   0.5
e0133               1.459 -0.284     7.113   0.998   0.8
e0258               0.622 -0.096     4.295   0.892   0.7
e0518               1.579  0.252     7.003   0.999   1.5
e0751               0.832 -0.133     4.008   0.951   0.6
e0598               1.503 -0.066     4.803   0.999   0.0
e0316               1.423  0.309     6.503   0.998   0.0

The two best of the noise, e0598 and e0316, have a measured Sharpe of 1.50 and 1.42 and a PSR(0) of 0.999 and 0.998: the same as e0133 and e0518, which do have an edge. By the Sharpe and its shape, nothing tells them apart. And two of the five good ones, e0258 and e0751, have a PSR(0) that grazes or falls short of 0.95: with 1,003 sessions, a true edge of 0.6-0.7 annual gets confused with noise. The kurtosis, from 4 to 7 in these series (with t innovations with 5 degrees of freedom), enlarges the denominator, although with daily Sharpes of the order of 0.1 its effect is small.

The Deflated Sharpe Ratio (DSR)

The right question for the best of N is not whether it beats 0, but whether it beats the maximum that N trials with no edge would give. The DSR is the PSR with the threshold at that expected maximum. Bailey and López de Prado (2014) approximate it with the dispersion of the Sharpes of the trials, V[{SR^n}]V[\{\widehat{SR}_n\}], and the same extreme value expression as in lesson 2.5.1:

SR0=V[{SR^n}][(1−γ) Φ−1 ⁣(1−1N)+γ Φ−1 ⁣(1−1Ne)],DSR=PSR^(SR0)SR_0 = \sqrt{V[\{\widehat{SR}_n\}]}\left[(1-\gamma)\,\Phi^{-1}\!\left(1-\tfrac{1}{N}\right) + \gamma\,\Phi^{-1}\!\left(1-\tfrac{1}{N e}\right)\right], \qquad DSR = \widehat{PSR}(SR_0)

with γ≈0.5772\gamma \approx 0.5772, the Euler-Mascheroni constant. The variance is that of the measured Sharpes of all the trials, not a theoretical one: if the trials are similar (highly correlated variants), their dispersion is smaller and the threshold goes down.

IN [03]python
def sr_max_esperado(n, var_sr):
    g = np.euler_gamma
    return np.sqrt(var_sr) * ((1 - g) * norm.ppf(1 - 1 / n)
                              + g * norm.ppf(1 - 1 / (n * np.e)))

def dsr(sr_i, n, var_sr, t, asim_i, curt_i):
    umbral = sr_max_esperado(n, var_sr) if n > 1 else 0.0
    return psr(sr_i, umbral, t, asim_i, curt_i)

var_sr = sr.var()
print(f"Dispersion of the 1,000 Sharpes: {np.sqrt(var_sr * 252):.3f} "
      f"annual (theoretical with no edge: {np.sqrt(252 / T):.3f})")
for n in (10, 50, 1000):
    print(f"N = {n:>4}: SR_0 = "
          f"{sr_max_esperado(n, var_sr) * np.sqrt(252):.2f} annual")

tabla["DSR(1000)"] = dsr(sr[elegidas], 1000, var_sr, T,
                         asim[elegidas], curt[elegidas])
tabla["DSR(50)"] = dsr(sr[elegidas], 50, var_sr, T,
                       asim[elegidas], curt[elegidas])
cols = ["annual_sharpe", "PSR(0)", "DSR(50)", "DSR(1000)", "real"]
tabla[cols].round(3)
OUT [03]The DSR, from scratch
Dispersion of the 1,000 Sharpes: 0.486 annual (theoretical with no edge: 0.501)
N =   10: SR_0 = 0.77 annual
N =   50: SR_0 = 1.11 annual
N = 1000: SR_0 = 1.58 annual
            annual_sharpe  PSR(0)  DSR(50)  DSR(1000)  real
estrategia
e0080               0.951   0.972    0.377      0.102   0.5
e0133               1.459   0.998    0.754      0.404   0.8
e0258               0.622   0.892    0.167      0.028   0.7
e0518               1.579   0.999    0.828      0.497   1.5
e0751               0.832   0.951    0.293      0.068   0.6
e0598               1.503   0.999    0.783      0.437   0.0
e0316               1.423   0.998    0.737      0.373   0.0

The measured dispersion of the 1,000 Sharpes, 0.49 annual, almost matches the theoretical one of a Sharpe with no edge over 1,003 sessions: as it should, because almost all of them are noise. With N = 1,000, the expected maximum of the noise is an annual Sharpe of 1.58, and no strategy beats it with 95% probability: e0518, the truly best one, stays at 0.50, and e0598, the best noise, at 0.44. The DSR does not separate them because the data does not separate them: with four years of data, a true Sharpe of 1.5 cannot be told apart from the maximum of a thousand noises. That is the honest conclusion, and it is the one the DSR would give with real data.

Fig. — PSR against 0 and Deflated Sharpe with 50 and 1,000 trials for the five synthetic strategies with an edge and the two best noise ones, with their true Sharpe

The figure puts the three probabilities of each strategy on the same scale. Against 0 (squares) almost all pass; with 50 trials (diamonds) none reaches 0.95; with 1,000 (circles), the best stays at 0.50. And the order of the rows, by measured Sharpe, mixes good ones and noise: the best noise is the second row.

The figure at the top shows the same calculation as a function of N: the DSR of the two strategies falls as the number of declared trials increases.

IN [04]python
ns = [1, 2, 3, 5, 7, 10, 15, 20, 30, 50, 70, 100, 150, 200, 300,
      500, 700, 1000]
curvas = pd.DataFrame({e: [dsr(sr[e], n, var_sr, T, asim[e], curt[e])
                           for n in ns] for e in ("e0518", "e0598")},
                      index=pd.Index(ns, name="N"))
print(curvas.loc[[1, 5, 10, 20, 50, 100, 1000]].round(3))
for e in curvas:
    ultimo = curvas.index[curvas[e] >= 0.95].max()
    print(f"{e}: DSR ≥ 0.95 up to N = {ultimo}")
OUT [04]DSR by number of trials
      e0518  e0598
N
1     0.999  0.999
5     0.977  0.966
10    0.948  0.928
20    0.905  0.874
50    0.828  0.783
100   0.757  0.705
1000  0.497  0.437
e0518: DSR ≥ 0.95 up to N = 7
e0598: DSR ≥ 0.95 up to N = 5

If e0518 had been the only idea tried, its Sharpe would be conclusive (DSR 0.999). Declaring ten trials, the DSR is already close to 0.95; with twenty, it falls short. The best noise has almost the same curve, a little lower. The practical lesson: the number of trials is part of the result, and declaring fewer than were made is a form of fraud, even if unintentional.

IN [05]python
autoval = np.linalg.eigvalsh(est.corr().to_numpy())
n_ef = autoval.sum() ** 2 / (autoval ** 2).sum()
print(f"Participation ratio: {n_ef:.0f} effective trials")
d = dsr(sr[["e0518", "e0598"]], round(n_ef), var_sr, T,
        asim[["e0518", "e0598"]], curt[["e0518", "e0598"]])
print(d.round(3).to_string())
OUT [05]An effective number of trials
Participation ratio: 147 effective trials
estrategia
e0518    0.715
e0598    0.660

With 147 effective trials, the DSR of e0518 is 0.72 and that of e0598, 0.66: between the two bounds and far from 0.95.

The probability of backtest overfitting (PBO)

The DSR needs N and the dispersion of the trials. If the returns of all the trials were also saved (a matrix of T sessions by N variants), you can directly measure what happens when choosing the best one. That is the idea of combinatorially symmetric cross-validation (CSCV) by Bailey, Borwein, López de Prado and Zhu:

  1. Split the matrix into S contiguous blocks of rows (S even; 16 here).
  2. For each of the (SS/2)\binom{S}{S/2} ways of choosing half of the blocks (12,870 with S = 16): that half is the “in-sample” set and the other, the “out-of-sample” set.
  3. Choose the variant with the best in-sample Sharpe and look at its out-of-sample relative rank, ω‾c\overline{\omega}_c (1 if it is the best of N, close to 0 if it is the worst), and its logit, λc=ln⁡(ω‾c/(1−ω‾c))\lambda_c = \ln\left(\overline{\omega}_c / (1 - \overline{\omega}_c)\right).
  4. The PBO is the proportion of partitions with λc≤0\lambda_c \le 0: the chosen one lands in the bad half out-of-sample.
T x N matrixS blocksPartitionsBest in-sampleOut-of-sample rankLogitPBO: logit ≤ 0
Mermaid source
flowchart TB
  M[T x N matrix] --> B[S blocks]
  B --> C[Partitions]
  C --> E[Best in-sample]
  E --> R[Out-of-sample rank]
  R --> L[Logit]
  L --> P[PBO: logit ≤ 0]

If the choice adds nothing, the out-of-sample rank of the chosen one is uniform and the PBO, 0.5. If the choice gets it right (the best in-sample tends to be the best out-of-sample), the logits are positive and the PBO, low. The partition is symmetric: in-sample and out-of-sample have the same size and each block is on each side half of the time.

IN [06]python
from itertools import combinations

def pbo_cscv(m, s=16):
    """m: T x N matrix of returns. Returns the PBO, the logits and,
    per partition, the in- and out-of-sample Sharpe of the chosen one."""
    t, n = m.shape
    bloques = np.array_split(np.arange(t), s)
    suma = np.array([m[b].sum(0) for b in bloques])
    suma2 = np.array([(m[b] ** 2).sum(0) for b in bloques])
    filas = np.array([len(b) for b in bloques])

    def sharpe(idx):
        k = filas[idx].sum()
        media = suma[idx].sum(0) / k
        return media / np.sqrt(suma2[idx].sum(0) / k - media ** 2)

    logits, sh_dentro, sh_fuera = [], [], []
    for dentro in combinations(range(s), s // 2):
        dentro = list(dentro)
        fuera = [i for i in range(s) if i not in dentro]
        a, b = sharpe(dentro), sharpe(fuera)
        j = np.argmax(a)
        w = (b < b[j]).sum() + 1        # rank from 1 (worst) to n
        w = w / (n + 1)
        logits.append(np.log(w / (1 - w)))
        sh_dentro.append(a[j]); sh_fuera.append(b[j])
    logits = np.array(logits)
    return ((logits <= 0).mean(), logits,
            np.array(sh_dentro) * np.sqrt(252),
            np.array(sh_fuera) * np.sqrt(252))

pbo, logits, sh_d, sh_f = pbo_cscv(est.to_numpy())
print(f"Partitions: {len(logits)}; PBO = {pbo:.3f}")
print(f"Sharpe of the chosen one: IS {sh_d.mean():.2f}, "
      f"OOS {sh_f.mean():.2f}; "
      f"P(OOS < 0) = {(sh_f < 0).mean():.2f}")
OUT [06]PBO via CSCV, from scratch
Partitions: 12870; PBO = 0.398
Sharpe of the chosen one: IS 2.36, OOS 0.14; P(OOS < 0) = 0.37
Fig. — Distribution of the logit of the chosen variant's out-of-sample rank across the 12,870 CSCV partitions (S = 16): 1,000 synthetic strategies above, 8 futures momentum lookbacks below

The PBO of the 1,000 strategies is 0.40: in four out of ten partitions, the best in-sample falls in the bad half out-of-sample. The mean Sharpe of the chosen one goes from 2.36 in-sample to 0.14 out-of-sample (the degradation), and in a little over a third of the partitions it loses money out-of-sample. Without the five strategies with an edge, the PBO gets close to 0.5:

IN [07]python
solo_ruido = est[ruido].to_numpy()
pbo_r, _, d_r, f_r = pbo_cscv(solo_ruido)
print(f"995 strategies with no edge: PBO = {pbo_r:.3f}; "
      f"Sharpe IS {d_r.mean():.2f}, OOS {f_r.mean():.2f}")
OUT [07]Noise only
995 strategies with no edge: PBO = 0.455; Sharpe IS 2.31, OOS 0.05

The five good ones lower the PBO by only about six points because, among a thousand, the best in-sample is usually noise: it is the same conclusion as the DSR, by another route. The figure shows, at the top, the logits of the 12,870 partitions of the full matrix: a bell centered slightly above 0, with a long left tail and 40% of the mass below 0, which is what the PBO measures. With a choice that got it right, the bell would be shifted to the right. The bottom panel is the case of the next section.

Reading the PBO carefully

The PBO answers a specific question: does choosing the best in-sample give something better than the median out-of-sample? It does not say whether the median makes money. The eight futures momentum variants of lesson 2.5.2 show it:

IN [08]python
fut = pd.read_parquet("futuros2_continuos.parquet")
precio = fut.pivot(index="fecha", columns="mercado",
                   values="cierre_ajustado")
rf = precio.pct_change(fill_method=None)
vol = rf.ewm(span=60, min_periods=60).std() * np.sqrt(252)
mom = {}
for n in (10, 21, 42, 63, 126, 189, 252, 378):
    senal = np.sign(precio.pct_change(n, fill_method=None))
    peso = (senal * 0.40 / vol / precio.shape[1]).shift(1)
    mom[f"mom{n}"] = (peso * rf).sum(axis=1, min_count=1)
mom = pd.DataFrame(mom).loc["2002":]
mezcla = pd.concat([mom, (-mom).add_prefix("rev_")], axis=1)

filas = {}
for nombre, m in (("8 momentum", mom),
                  ("8 + 8 reversal", mezcla)):
    p, _, d, f = pbo_cscv(m.to_numpy())
    filas[nombre] = {"PBO": p, "IS": d.mean(), "OOS": f.mean(),
                     "P(OOS < 0)": (f < 0).mean()}
pd.DataFrame(filas).T.round(3)
OUT [08]PBO of eight good variants and of sixteen mixed ones
                  PBO     IS    OOS  P(OOS < 0)
8 momentum      0.127  1.074  0.928       0.002
8 + 8 reversal  0.002  1.074  0.928       0.002

With eight momentum variants, all with an edge and highly correlated, the PBO is 0.13: in most partitions the chosen one also lands in the good half out-of-sample. The choice gets it right because in this data there is a true parameter (252) with a clear peak (lesson 2.5.2) and its neighbors, 189 and 378, are also good. The degradation is small, from 1.07 to 0.93, and the chosen one loses money out-of-sample in 0.2% of the partitions. That is the bottom panel of the logits figure: there are only eight possible rank values, and more than half of the partitions fall on the highest.

Now add eight reversal variants (the same ones with the sign flipped, all losers). The PBO drops to 0.002, but the chosen strategy is the same and so is its out-of-sample Sharpe: the choice “gets it right” more often because discarding obvious losers is easy. The PBO of a set of variants depends on which variants are put into it. With good and almost interchangeable variants, the opposite case, the PBO would rise toward 0.5 even if the family made money out-of-sample: which one comes first would be chance, and the choice would add nothing, but it would do no harm either.

SituationPBODegradationP(out-of-sample loss)Reading
A thousand trials, almost all noise0.402.36 to 0.140.37Overfitting: the choice is almost chance and the in-sample Sharpe, illusory
Good variants with a clear peak0.131.07 to 0.930.002The choice gets it right and the family has an edge
The same mixed with clear losers0.0021.07 to 0.930.002Lower PBO by construction of the set, not by merit
Good, interchangeable variantsClose to 0.5SmallLowChoice irrelevant; what works is the family

The rule: the PBO, degradation and the probability of loss are read together, and the set of variants is fixed before looking. A low PBO can be manufactured by adding bad variants; a high PBO with rare losses says that what works is the family, not the parameter.

What to record and how to report

The three tools need data that only exists if it was saved during research (lesson 2.2.4):

ToolWhat it needsWhat to report
PSRThe return series of the strategyPSR(0) and, if desired, PSR against a minimum useful Sharpe
DSRThe number N of trials and the dispersion of their SharpesDSR with the declared N and with a prudent N
PBOThe T × N matrix of returns of all the trialsPBO, degradation and probability of loss, with S

Three limitations to keep in mind. The DSR depends on N, and N is hard to count honestly: it includes ideas half discarded, variants tried by colleagues and those from the literature that inspired the strategy. The approximation of the expected maximum assumes independent trials; with correlated trials it is conservative. And the PBO, like CPCV, mixes in-sample and out-of-sample without respecting the arrow of time: it assumes the process is stable throughout the sample.

Exercises

  1. Compute the PSR of e0518 against an annual Sharpe threshold of 1 (a minimum useful value, not 0). What is the probability that its true Sharpe exceeds 1?
Solution
IN [09]python
umbral = 1 / np.sqrt(252)      # at daily frequency
p1 = psr(sr["e0518"], umbral, T, asim["e0518"], curt["e0518"])
print(f"PSR(1 annual) of e0518: {p1:.3f}")
OUT [09]PSR against an annual Sharpe of 1
PSR(1 annual) of e0518: 0.877

Against 1 annual, the probability drops from 0.999 to 0.88: a measured Sharpe of 1.58 over four years leaves an uncertainty of ±0.5 annual per standard error, and 1 sits just over one standard error away. Its true Sharpe is 1.5, so in this case the correct answer is “yes, it exceeds it,” but the data only says so with moderate probability. Testing against 0 answers whether there is any edge at all; against a minimum useful value, whether it is worth trading.

  1. Repeat the PBO of the 1,000 strategies with S = 8 and S = 12 blocks. Is the result stable?
Solution
IN [10]python
for s in (8, 12, 16):
    p, lg, _, f = pbo_cscv(est.to_numpy(), s=s)
    print(f"S = {s:>2}: {len(lg):>5} partitions, PBO = {p:.3f}, "
          f"P(OOS < 0) = {(f < 0).mean():.2f}")
OUT [10]PBO with other numbers of blocks
S =  8:    70 partitions, PBO = 0.371, P(OOS < 0) = 0.36
S = 12:   924 partitions, PBO = 0.425, P(OOS < 0) = 0.39
S = 16: 12870 partitions, PBO = 0.398, P(OOS < 0) = 0.37

The PBO moves a few points with S, because the size of the blocks and the number of partitions change, but the conclusion does not: in every case the chosen one lands in the bad half out-of-sample in a large proportion of the partitions (from 0.37 to 0.43). S is fixed before looking and reported along with the result.

  1. A fund presents you with a strategy with a Sharpe of 2.1 over six years of daily data and says it is “the only one they tried.” Its team has four researchers who have been working on variants for two years. What N would you use for the DSR, and what information would you ask for?
Solution

“The only one they tried” is almost never true in a team that has been doing research for two years: each researcher will have tried dozens of variants (horizons, filters, universes, costs). I would use a prudent N, in the hundreds, and compute the DSR with several values to see whether the conclusion depends on it. I would ask for the experiment log (dates, variants and results, including the discarded ones), the daily series of the strategy to estimate its skewness and kurtosis, and, if it exists, the matrix of returns of the variants to compute the PBO. If there is no log, the lack of information is itself a data point: the Sharpe cannot be deflated and should be treated with great caution.

Each answer is folded under its question

Self-assessment

  1. Strategy e0518 has an annual Sharpe of 1.58 over 1,003 sessions and a PSR(0) of 0.999. Why does its Deflated Sharpe with N = 1,000 trials drop to 0.50?

    • ABecause with 1,000 trials with no edge, the expected maximum Sharpe is around 1.58 annual: beating zero is not enough, you have to beat the best of the noise
    • BBecause its kurtosis is high
    • CBecause 1,003 sessions are too few for any test
    • DBecause the DSR only works for noise strategies
    Show answer

    Answer A. Because with 1,000 trials with no edge, the expected maximum Sharpe is around 1.58 annual: beating zero is not enough, you have to beat the best of the noise

    The DSR is the PSR with the threshold SR* at the expected maximum Sharpe of N candidates with no edge. With the measured dispersion of the 1,000 Sharpes (≈ 0.49 annual), that maximum is 1.58: e0518 sits right on it.

  2. What goes into the denominator of the PSR, and why?

    • AAnnual volatility, to annualize
    • BThe number of strategies tried
    • CThe risk-free rate
    • DSkewness and kurtosis: with fat tails or negative skewness, the estimated Sharpe is more uncertain and more evidence is needed
    Show answer

    Answer D. Skewness and kurtosis: with fat tails or negative skewness, the estimated Sharpe is more uncertain and more evidence is needed

    The standard error of the estimated Sharpe grows with kurtosis and with negative skewness: that is the expression Bailey and López de Prado use in the PSR. The number of trials enters the DSR through the threshold, not the denominator.

  3. In the CSCV on the 1,000 strategies, the PBO is 0.40. What does it mean?

    • AThat 40% of the strategies are overfitted
    • BThat in 40% of the partitions, the best in-sample strategy ends up below the median out-of-sample
    • CThat the chosen strategy loses 40% of its Sharpe
    • DThat there is a 40% probability of making money
    Show answer

    Answer B. That in 40% of the partitions, the best in-sample strategy ends up below the median out-of-sample

    The PBO is the frequency with which the logit of the out-of-sample rank of the chosen strategy is ≤ 0, that is, with which the chosen one lands in the bad half. With pure noise it would be ≈ 0.5; the five with an edge lower it a little.

  4. Eight futures momentum variants give a PBO of 0.13. Adding their eight inverses (reversal, all losers), the PBO drops to 0.002, with the same chosen variant and the same out-of-sample Sharpe (0.93). What do you conclude?

    • AThat the sixteen-variant version is a better strategy
    • BThat the PBO has a calculation error
    • CThat the PBO depends on the set of variants: adding obvious losers lowers it without improving anything. It is read together with degradation and the probability of loss, and the set is fixed before looking
    • DThat the reversal variants have an edge
    Show answer

    Answer C. That the PBO depends on the set of variants: adding obvious losers lowers it without improving anything. It is read together with degradation and the probability of loss, and the set is fixed before looking

    The PBO measures whether the selection process gets the ranking right, not whether the variants make money. Discarding obvious losers is easy, so adding them “improves” the PBO without changing the chosen strategy or its result: that is why it cannot be read on its own.

  5. What must be saved during research to be able to compute the DSR and the PBO at the end?

    • AThe number of variants tried and the return series of each one, including the discarded ones
    • BOnly the best strategy
    • COnly the Sharpe of each variant
    • DNothing: they are computed with the final strategy
    Show answer

    Answer A. The number of variants tried and the return series of each one, including the discarded ones

    The DSR needs N and the dispersion of the Sharpes of the trials; the PBO, the full T × N matrix of returns. If only the winner is saved, the correction is impossible (lesson 2.2.4).

Key points

  • The PSR turns a Sharpe into a probability that the true one exceeds a threshold, taking into account the length of the series, skewness and kurtosis.
  • The DSR is the PSR with the threshold at the expected maximum Sharpe of N trials with no edge. With the 1,000 trials of ruido2, that maximum is 1.58 annual and no strategy beats it with 95%.
  • The best noise (e0598, Sharpe 1.50) and the best with an edge (e0518, 1.58) have DSRs of 0.44 and 0.50: with this data they are indistinguishable. Declaring 7 trials or fewer, e0518 would pass the 95%.
  • The CSCV PBO counts how often the best in-sample falls in the bad half out-of-sample: 0.40 in ruido2, with the chosen strategy's Sharpe going from 2.36 in-sample to 0.14 out-of-sample.
  • The PBO is read with degradation and the probability of loss, and the set of variants is fixed beforehand: adding eight obvious losers lowers the futures momentum PBO from 0.13 to 0.002 without changing anything.
  • Without a record of all the trials (N and their returns) no correction is possible.

Resources

Documentation