QuantLab / Validation

Purging and embargoing explained visually

Why the gap between train and test has to be at least as wide as the label horizon, and what purging and embargoing each remove.

Applied7 min readUpdated purgingembargocross-validationleakage
5
1

purge gap = max(embargo, horizon) = 5 rows

A chronological split solves the shuffling problem described in why random train/test splits break on time series, but it leaves one gap untreated: the handful of rows sitting exactly at the seam between training and test. Those rows are where a leakage-safe pipeline does two more things — purgingPurge Removing training rows whose label window overlaps a test block, so a model never trains on information the test period's outcome depends on. and embargoingEmbargo Removing the rows immediately after a test block, where serial correlation would otherwise let information leak backward into training. — that a plain chronological split does not do on its own.

The picture

Imagine a single timeline of daily rows running left to right, split into a training block and a test block at some date. Picture three horizontal bands stacked under that timeline.

The top band is the raw chronological split: everything before the cut date is training, everything after is test. Nothing is removed.

The middle band shows what a label horizonLabel horizon The number of future periods a label looks ahead to determine its outcome; it sets the minimum purge gap a validation split must respect. of, say, 5 days actually means for each row: draw a short forward-pointing bar starting at each row and extending 5 days to the right, representing the window of future prices that row’s label was built from. Every training row whose forward bar crosses the cut date is now flagged — its label depends on price information that falls inside the test period. Removing those flagged rows from training is purging. The purged rows are not test rows; they are training rows that have to be dropped because keeping them would let the model see into the test window through the back door of its own label.

The bottom band shows the mirror problem: not the labels, but the rows themselves are still serially correlated across the boundary. Even a training row with a label built entirely from past data is highly informative about the value of the row immediately following it, because return series carry autocorrelation in volatility and, in many strategies, in signal state. Removing a further block of rows on the far side of the test window — before test-period patterns can leak back into a later training block, and before test rows can be predicted almost trivially from the training rows that were their immediate chronological neighbours — is embargoing.

Purging removes forward-looking label contamination. Embargoing removes serial-correlation contamination. They solve different problems and neither substitutes for the other.

The gap size rule

Quant Compose sizes the removed gap as:

purge_gap=max⁡(embargo,target_horizon)\text{purge\_gap} = \max(\text{embargo}, \text{target\_horizon})

In the workspace’s pipeline defaults, embargo is 5 (clamped 0–60) and target horizon is 1 (clamped 1–60). With those defaults, purge_gap = max(5, 1) = 5 — the embargo happens to dominate. But raise the label horizon to 10 days while leaving embargo at its default of 5, and the gap becomes max(5, 10) = 10: the horizon now dominates, and a pipeline that only purged 5 rows because “that’s the embargo setting” would still be leaking 5 days of label overlap.

A worked example

Take a dataset of daily rows and a train/test cut at row 700 of 1,000. Target horizon h=5h = 5, embargo e=5e = 5, so purge gap g=max⁡(5,5)=5g = \max(5, 5) = 5.

RegionRowsTreatment
Training1–694Used to fit
Purge zone695–699Removed — labels overlap the test window
Test700–999Held out, scored
(Embargo, if test precedes further training)next 5 rows after test endRemoved before any subsequent training block resumes

Five rows disappear from the usable dataset at this single boundary — not because they are bad data, but because keeping them would let the evaluation quietly borrow information it is not supposed to have. In a single chronological split this is a small, one-time cost. In walk-forward validationWalk-forward validation A sequence of out-of-sample test periods with refitting at each origin, so every reported result is on data the model never trained on., where the boundary recurs at every fold, and especially in combinatorial purged cross-validationCombinatorial purged cross-validation Many purged, embargoed train/test arrangements built from combinations of time groups, producing a distribution of out-of-sample results instead of one., where boundaries recur between every pair of adjacent groups, the same purge-and-embargo logic is applied at every one of those seams — see walk-forward validation, fold by fold for how the gap compounds across a full fold sequence.

What happens if a researcher raises the label horizon without noticing

The most common way this rule gets violated in practice is not a wrong purge implementation — it is a researcher changing the target from “next-day direction” to “20-day-forward return” late in a project, after the validation setup was configured for a 1-day horizon. If the pipeline purge gap is not re-derived from the new horizon, the last 19 days of every training block now silently overlaps the test window’s labels, and every metric that follows is contaminated by exactly the amount described in why random train/test splits break on time series.

pipeline_warnings() in the workspace generates a plain-English warning whenever the label horizon exceeds one period, specifically because an overlapping label reduces the effective, independent sample size even when purging is correctly applied — purging removes the leak, but it does not manufacture independent observations out of overlapping labels that remain inside the training set itself. That is a separate concern, addressed by treating overlapping labels’ effective weight rather than their raw count — see the note on label uniquenessLabel horizon The number of future periods a label looks ahead to determine its outcome; it sets the minimum purge gap a validation split must respect. in the workspace’s method catalogue.

Purging and embargoing across many boundaries at once

The single-split picture above has one boundary and one gap. Combinatorial purged cross-validation has many. The workspace’s default CPCV configuration splits the data into 6 groups and forms test blocks of 2 groups at a time, written (N,k)=(6,2)(N, k) = (6, 2). The number of ways to choose 2 groups out of 6 to hold out is:

(62)=6!2! 4!=15\binom{6}{2} = \frac{6!}{2!\,4!} = 15

Fifteen splits — fifteen distinct train/test arrangements, each with its own purge-and-embargo treatment applied at every boundary between a training group and a test group. But 15 splits are not 15 independent backtest paths. Because the test groups overlap across different splits — group 3 appears as part of the test block in several of the 15 arrangements — the number of genuinely independent equity-curve paths that can be reconstructed is smaller:

φ=(N−1k−1)=(51)=5\varphi = \binom{N-1}{k-1} = \binom{5}{1} = 5

Five independent paths, not fifteen. Reporting a CPCV result as “evidence across 15 out-of-sample paths” would count each underlying observation roughly three times over and overstate the strength of the evidence threefold — a mistake the workspace’s stitching logic avoids by falling back to split-level dispersion rather than a fabricated path count whenever the arrangement does not support genuine path reconstruction.

Purging and embargoing are not optional extras

It is tempting to treat purging and embargoing as a refinement layered on top of an already-working chronological split — something to add once the basic pipeline runs. The arithmetic in why random train/test splits break on time series shows why that framing is backwards: without the gap, a chronological split still leaks at its boundary, just less severely than a fully random one. The gap is not a refinement. It is the part of leakage-safe validation that a plain chronological split leaves out.