pbo¶
Defined in fynance.research
- pbo(returns_panel, n_blocks=16, metric=None)[source]
Probability of backtest overfitting via combinatorially symmetric CV.
Implements the CSCV procedure of Bailey, Borwein, Lopez de Prado & Zhu (2015): split the
Tperiods inton_blockscontiguous blocks; for every way to assign half the blocks to an in-sample (IS) half and the other half to the complementary out-of-sample (OOS) half (\(\binom{n\_blocks}{n\_blocks / 2}\) splits total), pick the config that scores best on the IS half bymetric, then locate that same config’s OOS-half performance among all configs’ OOS performances as a relative rank \(r \in (0, 1)\) (average-tie rank divided byn_configs + 1, so the best OOS config sits close to 1 and the worst close to 0). The rank logit is \(\lambda = \ln(r / (1 - r))\);pbois the share of splits with \(\lambda \le 0\), i.e. where the in-sample winner performs at or below the OOS median — a config search that overfits systematically sends the IS winner to the bottom half OOS.- Parameters:
- returns_panelnumpy.ndarray, shape (T, n_configs)
Per-period returns, one column per configuration tried (see
returns_panelto build this fromExperimentruns).- n_blocksint
Number of contiguous blocks to split
Tinto. Must be even (a balanced IS/OOS split) and no greater than 16 — beyond that \(\binom{n\_blocks}{n\_blocks/2}\) exceeds 12,870 splits.- metriccallable, optional
metric(returns) -> floatscoring a 1-D per-period return stretch, higher is better. Defaults to an annualization-free Sharpe (mean over std of the returns) — deliberately notfynance.metrics.sharpe, which assumes a price/equity curve rather than a returns series.
- Returns:
- PBOResult
The overfitting diagnostics (see
PBOResult).
- Raises:
- ValueError
If
returns_panelis not 2-D,n_blocksis odd,n_blocksimplies more than 12,870 IS/OOS splits, orT < n_blocks.
Examples
>>> import numpy as np >>> from fynance.research import pbo >>> rng = np.random.default_rng(0) >>> panel = rng.normal(0.0, 0.01, size=(400, 8)) # pure noise, no edge >>> result = pbo(panel, n_blocks=8) >>> 0.0 <= result.pbo <= 1.0 True >>> result.logits.shape (70,)