← All examples

Machine Learning: Financial ML (López de Prado)

Every other subsection asks how to build a model. This one asks how to stop fooling yourself once you have built one, and it exists because financial data breaks assumptions that most machine-learning tooling takes for granted.

Labels overlap, so ordinary cross-validation hands the model its own test set. Prices are non-stationary, so the standard fix — differencing to returns — throws away the memory the signal lives in. Outcomes are path-dependent, so a fixed-horizon label misrepresents what a trade actually does. And a strategy is rarely the first one tried, so a Sharpe ratio computed on the winner of a search is not the Sharpe ratio of a strategy.

Each tool here answers one of those, and each is checked the way the rest of the collection checks things: in a simulated world where the right answer is known by construction, because a method that only ever runs on real data can never be caught being wrong.

Labels that last, and what they do to cross-validation. Outside finance an observation and its label arrive together. Here a label is the outcome over a forward window — the return over the next ten days, or which price level a trade reaches first — so consecutive labels are built from overlapping stretches of the same history and are strongly correlated. That is what makes ordinary cross-validation unsafe. Shuffling rows scatters each test observation’s near-duplicate neighbours through the training set, and the model is effectively shown its own answers: leakage, information reaching the model at fit time that would not have been available when the forecast was due. Two repairs follow. Purging removes from training every observation whose label window overlaps the test window at all. Embargo additionally drops a block immediately after each test fold, to catch dependence that outlives the label horizon. Scores here are read as AUC — the area under the receiver-operating-characteristic curve, the probability a randomly chosen positive case is ranked above a randomly chosen negative one, so 0.5 is a coin flip and any excess over it, in a world built to contain no signal, is leakage measured directly.

Stationarity, memory, and the quantity in between. Most statistical machinery assumes a stationary series — one whose mean, variance and autocorrelations do not drift over time. Prices are not stationary; they wander, and a model fitted on one price range extrapolates blindly outside it. The standard fix is to difference: model returns instead of levels. That works and it is expensive, because differencing once erases almost all of the series’ memory — the long-range dependence between distant observations, which is where the slow structure lives. Fractional differentiation is the compromise: difference by a fractional amount d, keeping as much memory as stationarity permits. Judging whether the result is stationary is the hard part. The two standard tests point opposite ways — ADF (the augmented Dickey–Fuller test) takes non-stationarity as its null, and KPSS (Kwiatkowski–Phillips–Schmidt–Shin) takes stationarity as its null, so a series is convincingly stationary only when both concur. Neither is built for this case, and the reliable route is to estimate the order of integration directly: a series is said to be I(d) if differencing it d times yields a stationary series, and a fractionally integrated series is stationary exactly when d < 0.5.

Labelling a trade by what happens to it. A fixed-horizon label asks what the return was after ten days, which ignores everything that happened in between — including a stop-loss that would have closed the position on day two. Triple-barrier labelling instead sets three exits, a profit-take above, a stop-loss below and a time limit, and labels the trade by whichever it touches first. The label is therefore path-dependent: it depends on the route the price took, not only on where it ended. Meta-labelling then splits the problem in two — a primary model decides the direction, and a second model decides whether to act on that call at all, learning where the primary is trustworthy. Because the second model only ever declines bets, the metrics move in a particular way: precision (the share of acted-on bets that were right) rises, recall (the share of winning opportunities actually taken) falls, and F1, which combines the two, can stay perfectly flat while the economics change completely.

The last mistake: the strategy you are looking at is the one that survived a search. A Sharpe ratio is return per unit of risk — average return divided by the volatility of those returns, annualised so that daily, weekly and monthly strategies can be compared on one scale. Its weakness is that it says nothing about how many candidates were tried before this one. Test enough configurations on pure noise and the best will post a Sharpe that looks like skill, because the maximum of many random draws is large by construction. Two corrections address this. The deflated Sharpe ratio asks not whether the true Sharpe beats zero but whether it beats the expected maximum from N trials, adjusted for how skewed and heavy-tailed the returns are and how long the record is. The probability of backtest overfitting needs no trial count at all: CSCV (combinatorially symmetric cross-validation) splits the timeline every balanced way, picks the in-sample winner in each split, and records how that winner ranks out of sample. Both come with a subtlety the examples measure rather than assume — the trial count that matters is the number of effectively independent ideas, which on strategies that hold nearly the same positions is far smaller than the number of configurations run.

Four tools, each tested where the right answer is known in advance

Every value is committed notebook output. A is the purged cross-validation example, B fractional differentiation, C triple-barrier labelling on the left and the deflated Sharpe ratio on the right. Each panel carries its own measure and its own axis.

A · Thirty worlds built to contain no signal at all, so the true AUC is exactly 0.500 truth = 0.500 0.5 0.6 0.7 0.8 mean AUC across 30 worlds, with 95% intervals shuffled k-fold 0.7919 +0.292, t = 97 TimeSeriesSplit 0.4905 −0.010, t = −2.0 purged, no embargo 0.5037 +0.004, t = 0.7 purged + embargo 0.5053 +0.005, t = 1.0 true forward holdout 0.4898 −0.010, t = −1.4 B · Fractional differencing: how much memory survives, and where stationarity actually begins 0.00 0.25 0.50 0.75 1.00 d = 0 d = 0.25 d = 0.5 d = 0.75 d = 1 ADF picks 0.35 stationarity begins at 0.50 measured integration order memory kept (correlation with the price level) 0.5 shaded band: not stationary At the order ADF selects, the series still measures I(0.630) — above the boundary, so it is not stationary at all. The order that genuinely crosses is 0.50, and it still keeps 0.58 of the price-level correlation, against 0.05 for returns. C · Two diagnostics calibrated against a known answer before being trusted 0.45 0.55 0.65 act on everything 0.45 0.50 0.55 0.58 0.60 meta-model precision, by confidence threshold threshold LEFT: every interval contains the do-nothing baseline. The apparent upward slope is 46 events at the selective end, not a filter finding anything — re-run through the purged splitter the lift is +0.005 against a fold-to-fold spread of 0.071, sign unstable. 0.0 0.5 1.0 0.00 0.00 0.63 0.00 1.59 0.37 3.17 0.97 the illustration's edge P(DSR clears 0.95) true annualised Sharpe RIGHT: correctly calibrated under the null, and modest in power — at the edge the illustration calls skilled it never fires once. A failed DSR means unproven, not worthless.

A is the section’s method in one picture. The experiment is a world constructed to contain nothing: random-walk features, labels drawn from a forward window of pure noise, so the true AUC is 0.500 by construction and every point above it is leakage rather than skill. Shuffled k-fold returns 0.7919 — it invents nearly three-tenths of AUC out of an empty room, at a t-statistic near 100, so this is a property of the procedure and not a bad draw. Purged cross-validation is indistinguishable from the truth. Two details are worth having, because a single run implied otherwise: TimeSeriesSplit is not the co-defendant — its contiguous folds leak at one boundary only, and it lands slightly below 0.5 — and the embargo adds nothing measurable here, because this simulation’s noise is independent beyond the label window. The catastrophe belongs specifically to shuffling.

B is the panel that dismantles a rule most practitioners apply without checking. The standard recipe is to pick the smallest differencing order whose ADF test clears 0.05. On this series that returns d = 0.35 — and at 0.35 the series still measures I(0.630), well above the 0.5 boundary at which a fractionally integrated series becomes stationary at all. The rule selects a non-stationary series and reports success. Estimating the integration order directly puts the crossing near 0.50, and the consolation is that the honest answer is barely more expensive: at 0.50 the series still carries 0.58 of its correlation with the price level, against 0.05 for plain returns. The method survives intact; only the selection rule fails.

C is the same discipline applied to two claims that look like results. On the left, a meta-model’s precision rises as it becomes more selective — the shape everyone hopes to see. Attach 95% intervals and every one of them contains the do-nothing baseline; the rise at the selective end is 46 events, not a discovery. On the right, the deflated Sharpe ratio swept against a known injected edge. It is correctly calibrated under the null and it is weak: at the 0.63 Sharpe the illustration calls “genuinely skilled” it never once clears 0.95, needing about 1.6 for a one-in-three detection rate and near 3 to be reliable. That reframes the illustration’s apparent success as a failure to detect a real edge, and gives the tool its correct interpretation: a strategy that fails the DSR has not been shown to be worthless, only unproven.

How the four tools relate

Four different failures, one shared response. Each tool fixes a specific way that financial data breaks an assumption the standard toolkit takes for granted, and each is validated in a world where the answer is known by construction rather than on data where nobody can be caught out.

The order is not arbitrary: each tool is used by the ones after it. Triple-barrier labelling produces exactly the overlapping labels that the purged splitter exists to handle, and the meta-labelling result is re-run through that splitter before being believed. The fractionally differenced price becomes a feature in the ML capstone, where the order chosen here turns out to be the one that also maximises out-of-sample accuracy. And the deflated Sharpe ratio is the last gate on anything the other three produce. Read in reverse, the subsection is a list of the ways a finished backtest can be wrong before anyone has questioned the model itself.

Purged & Embargoed Cross-Validation — Why Ordinary CV Lies

A label built from a forward window shares information with its neighbours, so a shuffled fold hands the model observations that overlap its own test set. The splitter is built from scratch and then put in a world with no signal at all — random-walk features, labels from pure noise, true AUC exactly 0.500 — and run across thirty independent draws rather than one. Shuffled k-fold invents +0.29 of AUC out of nothing, a t-statistic near 100; purged cross-validation is statistically indistinguishable from the truth (bias +0.005, t = 1.0). Repeating rather than running once also corrects two things a single draw implied: TimeSeriesSplit is not the co-defendant — contiguous forward folds confine the leak to one boundary, and it reads 0.490, if anything below the truth — and the embargo contributes nothing measurable in a simulation whose noise is independent beyond the label window. On real S&P data every method returns a coin flip, which is stated as the expected result rather than dressed into a story: where there is no signal, there is nothing to leak.

View example →

Fractional Differentiation — Stationarity Without Erasing Memory

Differencing a price series a fractional amount buys stationarity without destroying the memory that returns throw away. The binomial weights are built from scratch and match the closed form to 1.1×10−16. What does not survive is the standard rule for choosing the order. The ADF p-value is not monotone near its threshold, so the answer moves with the grid — 0.35, 0.31, or 0.25 depending on implementation. And KPSS disagrees with ADF at every d the rule might select: the two tests concur only at the ends, on the raw price and on returns. Simulating series of known integration order shows why neither settles it: ADF calls a non-stationary I(0.8) process stationary and KPSS calls a stationary I(0.2) process non-stationary. Estimating the memory parameter directly resolves it — the ADF-selected series measures I(0.63) and is not stationary; the boundary is crossed near d = 0.50, where 0.58 of the price-level correlation survives against 0.05 for returns. The method holds; the selection rule does not.

View example →

Triple-Barrier Labelling & Meta-Labelling

Labelling a trade by which of a profit-take, stop-loss or time barrier it touches first — and the barrier width turns out to be the method rather than a parameter. At the width used, 77.5% of labels are path-determined; widen them to two standard deviations of the full holding-period move and only 6.4% are, collapsing back into the fixed-horizon labelling the method exists to replace. Meta-labelling then needs the right scorecard: precision rises from 0.57 to 0.78 while F1 stays flat, because the primary’s recall was 1 to begin with — expected profit is the metric that moves, roughly doubling. On the real market, giving the precision-vs-selectivity curve error bars dissolves it: no threshold clears its own sampling error, and re-run through the purged splitter the lift is +0.005 against a fold-to-fold sd of 0.071, with the sign unstable. A curve of that shape will almost always look like it slopes upward.

View example →

Deflated Sharpe & Backtest Overfitting

Search enough configurations and the winner looks spectacular on luck alone: 100 pure-noise strategies produce a best annualised Sharpe of 1.00 against an expected maximum under pure chance of 1.03. Both corrective tools are then calibrated against known answers rather than read off one draw. The DSR turns out to have modest power — it never clears 0.95 at a true Sharpe of 0.63, needs about 1.6 for a one-in-three detection rate and near 3 to be reliable — so the illustration’s “skilled” case at 0.66 is the test failing to detect a real edge. And PBO’s null is 0.50, not zero, confirmed at 0.52 by replication. On real crossovers the 35 trials are only 1.7 effectively independent, and correcting for that moves the DSR from 0.68 to 0.91. The R companion is a cross-check that fails: the CRAN package reports 0.012 where two from-scratch implementations report 0.31, traced to the out-of-sample ranking step.

View example →