Why standard k-fold overstates quant model skill, how purging and embargo remove label-overlap leakage, and a sklearn-style Python implementation with K and embargo tuning.

Standard k-fold shuffles observations at random, which trains on information from the future when labels overlap in time or returns are serially correlated, producing inflated validation scores that collapse live.
Classical cross-validation assumes observations are independent and identically distributed, so any random partition is representative. Financial ML violates both halves: volatility clusters, events cluster, and supervised labels such as next-N-day returns or triple-barrier outcomes span a holding horizon.
Concretely, a training sample dated Monday with a 10-day label already contains price action from most of the following two weeks. If your test fold covers that same fortnight, the model has effectively seen the answer during training.
| Scheme | Split rule | Failure mode in finance |
|---|---|---|
| Standard K-Fold | Random shuffle into K folds | Trains on labels overlapping the test window; leaks future |
| Purged K-Fold | K folds + purge overlapping labels + embargo | Honest error estimate for model selection |
| Walk-Forward | Rolling train window, forward-only test | Honest production simulation, single path |
Leakage enters through label overlap (a training label window reaching into the test period) and through serial correlation (adjacent samples sharing microstructure noise, volatility regime, or event effects).
Label overlap is mechanical. Each sample has a feature time t0 and a label end time t1 (for example, the time a take-profit, stop-loss, or vertical time barrier is first touched). Whenever [t0, t1] of a training row intersects the test fold interval, that row carries test-period returns and must go.
Serial correlation is statistical. Even with non-overlapping labels, the sample right after a test fold shares the same volatility regime and order-flow conditions. That is what the embargo handles: a dead zone after each test fold that is never used for training.
Split into K folds, purge training rows whose label spans touch the test fold, embargo a small fraction after each test fold, then fit and score. Repeat for all K folds and average.
This is Lopez de Prado (2018, Chapter 7) with one practical addition: size the embargo from your longest label horizon rather than guessing a fixed percent.
You need three arrays per sample: feature time t0, label end time t1, and the embargo length. The splitter below yields purged, embargoed train indices with a sklearn-compatible interface.
Keep folds contiguous in time and purge on label time, not feature time. The snippet below assumes t0 and t1 are integer positions (or timestamps you have already mapped to positions) and that folds are contiguous blocks.
import numpy as np
def purged_kfold(n, n_splits, t1, embargo_pct=0.01):
"""Yield (train_idx, test_idx) with purging + embargo.
n: number of samples, t1: array of label end positions.
"""
idx = np.arange(n)
folds = np.array_split(idx, n_splits)
embargo = int(n * embargo_pct)
for k, test in enumerate(folds):
t0_test, t1_test = test[0], test[-1]
# purge: drop train rows whose label spans into test window
mask = np.ones(n, dtype=bool)
mask[test] = False
overlap = (idx >= t0_test) & (idx <= t1_test)
label_touch = np.array(
[(t >= t0_test and s <= t1_test) for s, t in zip(idx, t1)]
)
mask = mask & ~label_touch
# embargo: drop h samples immediately after test fold
mask[t1_test + 1 : t1_test + 1 + embargo] = False
_ = overlap # documents the test window for audit logs
yield idx[mask], test
# usage: t1[i] = end bar of sample i's label (e.g. barrier touch)
# for tr, te in purged_kfold(len(X), 5, t1, embargo_pct=0.01):
# model.fit(X[tr], y[tr]); score = model.score(X[te], y[te])Start with K = 5-10 and embargo at ~1% of the sample or one maximum holding period. Smaller K wastes data; larger K increases purge cost and score variance.
There is no universal K. Fewer, larger folds mean each test set is a meaningful market regime but leave less training data; many small folds mean cheaper purges per fold but noisier scores and more boundary effects. Report the full distribution across folds, not the maximum.
| Choice | Too small | Too large |
|---|---|---|
| K (folds) | Test fold is one regime; metric has high bias | Thin folds, heavy purging, high variance |
| Embargo h | Residual autocorrelation leaks across boundary | Throws away good data; train set shrinks |
| Label horizon | Short horizon misses the trade; weak signal | Long horizon forces massive purges |
Purged k-fold selects models, walk-forward simulates production through time, and combinatorial purged CV (CPCV) estimates the probability that your winner is overfit.
These are complements, not substitutes. A healthy pipeline runs purged k-fold for feature and hyperparameter decisions, then a walk-forward pass with periodic refits to produce the stitched out-of-sample equity curve investors actually experience.
When you have tried many configurations, add CPCV: it builds many backtest paths from combinations of purged folds and computes the probability of backtest overfitting (PBO). A high PBO kills the strategy no matter how good the best path looks. See the CPCV glossary entry for the combinatorics.
Purge the train set only, embargo after (not before) the test fold, never shuffle time order, and size the embargo from the data rather than hard-coding zero.
Most purged-CV failures are implementation slips, not theory problems. Run through this list before promoting any model.
Standard k-fold shuffles i.i.d. observations, but financial labels overlap in time and returns are serially correlated, so training rows contain test-period information and validation scores are optimistically biased.
Purging removes training samples whose label window overlaps the test window. Embargo additionally removes samples immediately after the test window to absorb residual serial correlation and event clustering.
A common starting point is ~1% of the sample or roughly one maximum label holding period, whichever is larger. Scale it with measured autocorrelation and label horizon, and sensitivity-check the result.
Use both: purged k-fold for model and hyperparameter selection, walk-forward with periodic refits to simulate production behavior. Add combinatorial purged CV when you need an overfitting probability across many trials.
Coming soonQuantitative analytics platform
Put this research into production. A £10 reservation locks the £400 launch price (vs £490 public) and priority access in payment order.