Purged k-Fold: The Derivation
When does a training sample leak into the test set? The answer depends on three intervals: the label window, the feature window, and the test window. Here is the full geometric derivation.
Purged k-Fold: The Derivation From First Principles
The previous article explained why standard CV fails for time series. This article derives exactly when to purge. The result is a precise geometric rule: a training observation leaks into a test fold if and only if its label window or its feature window intersects the test window. The purge width depends on three intervals: the label horizon , the feature lookback , and the size of the test fold.
The derivation is straightforward. The rules that come out of it are often violated in practice. Most “purged CV” implementations get the geometry wrong, purging too little (allowing leakage) or purging too much (wasting data). The correct rule is precise.
(López de Prado, M., Advances in Financial Machine Learning, §7.4.2. ISBN 978-1119482086.)
The Setup
We have a time series of observations indexed by time . Each observation has:
- A label window : the time interval over which the label is computed (e.g., the holding period for the trade).
- A feature window : the time interval over which the features are computed (e.g., the rolling lookback for momentum).
The label at time depends on data in . The features at time depend on data in .
The test fold is a contiguous interval of times. The training set is everything else.
We want to know: for which training observations does the test fold “leak” into the observation’s label or features?
The Two Leakage Channels
There are two channels by which a training observation can leak test information.
Channel 1: Label leakage.
The label at time depends on data in . If this interval intersects the test fold , then the label is contaminated by test-period data. The training observation has been labeled using information from the test fold.
Channel 2: Feature leakage.
The features at time depend on data in . If this interval intersects the test fold, then the features are contaminated by test-period data. The training observation has features computed using information from the test fold.
A training observation must be purged if either channel leaks. Both channels must be clean for the observation to be safe.
Channel 1: Label Leakage in Detail
We want to find all such that .
Two intervals and intersect if and only if and . Equivalently, the maximum of the left endpoints is less than or equal to the minimum of the right endpoints.
Applying this:
For training observations (, since training is before the test fold), this simplifies to:
So label leakage affects training observations in .
The width of the label-leakage interval is exactly (the label horizon).
(AFML, Eq. 7.1.)
Channel 2: Feature Leakage in Detail
We want to find all such that .
Same logic:
For training observations ():
So feature leakage affects training observations in .
The width of the feature-leakage interval is (slightly less than the feature window, because the upper endpoint of the feature window is , not ).
A subtle point: this is the leakage from the leading edge of the test fold (data just after the test fold starts). For training observations strictly before the test fold, the feature window is entirely before the test fold. For training observations just after the test fold, the feature window reaches back into the test fold. The asymmetry is why we have embargo (next article) rather than just purging.
Combining the Two Channels
A training observation is purged if either channel leaks. The combined purge interval is:
But wait: the training set is strictly before the test fold in walk-forward CV. The interval is after the test fold, so it is not in the training set anyway. The relevant leakage interval is just:
So the purge width for training observations is , the label horizon.
This is the rule: drop training observations in the days immediately preceding the test fold.
(AFML, §7.4.2.)
The Embargo Direction
The interval is for post-test training data. In walk-forward CV, there is no such data. In rolling CV (where the training set has data after the test fold) or in refit scenarios (where you retrain on data after the test fold), the post-test data has feature leakage.
This is the embargo case. The embargo width is (or , depending on convention). The embargo is on the trailing edge of the test fold, in the post-test training data.
The next article covers embargo in detail. For now, the rule: in walk-forward CV, purge days on the leading edge. In rolling CV or refit scenarios, also embargo days on the trailing edge.
A Geometric Visualization
Picture the time axis. Test fold is from to .
|--h--| |---w---|
--------[-----]--------------------------------[-----]---------
T_s-h T_s T_e+1 T_e+w
PURGE EMBARGO
The purge zone is the days immediately before the test fold. The embargo zone is the days immediately after. A training observation in the purge zone has a label that extends into the test fold. A training observation in the embargo zone has features that reach back into the test fold.
In walk-forward CV (expanding window, no retraining within the test fold), only the purge applies. The training set ends at (or earlier if you started earlier).
In rolling CV (fixed-size training window) or refit scenarios, both apply. The training set has a gap from to (approximately, with overlap depending on rolling window size).
The Special Case of Triple-Barrier Labels
The derivation above assumes the label window is exactly . For triple-barrier labels (previous article), the label window is dynamic: it ends at whichever barrier (upper, lower, or vertical) is hit first.
The vertical barrier is days. The dynamic barriers may be hit sooner. The label window is where .
For the purge derivation, use the maximum possible label window, which is . This is conservative: it may purge more than strictly necessary, but it guarantees no label leakage.
In practice, the difference is small. If your vertical barrier is 5 days and your dynamic barriers are hit within 1-2 days on average, the conservative purge uses instead of the average . The conservative rule is preferred for safety.
(AFML, §3.6.)
The Concurrency Adjustment
A further refinement: when many observations have overlapping label windows, the unique information content is small (recall the uniqueness weights from the sample weights article). The purge zone should account for this.
If your dataset has concurrent labels per observation (i.e., each label overlaps with others on average), the effective label horizon is roughly . The purge width should be scaled by :
This is a heuristic, not a strict derivation. But it captures the intuition that overlapping labels create compound leakage that a simple -day purge does not address.
(AFML, §4.5.2.)
What About the Test Fold Itself?
The test fold is held out from training, so the leakage from training into test is what we care about. But what about leakage from test into subsequent evaluation?
In a walk-forward CV with a single test fold, this is not an issue: the test fold is evaluated once, and the next fold starts fresh. In CPCV with multiple test folds, the same observation may be in the test set of multiple paths. The Sharpes are correlated because of this.
For purging purposes, treat each test fold independently. The test fold’s labels are not used to train, so there is no leakage from test into training. The leakage is only from training into test (via overlapping features or labels).
Implementation
A clean implementation:
def purge_train_obs(train_times, test_start, test_end, h, w=0):
"""Drop training observations that leak into the test fold.
Label window: [t, t+h]
Feature window: [t-w, t-1]
Test fold: [test_start, test_end]
Returns: filtered training times.
"""
# Label leakage: training obs with label window overlapping test
# For training obs (t < test_start), this is t in [test_start - h, test_start]
label_leak = (train_times >= test_start - h) & (train_times <= test_start)
# Feature leakage (post-test, only relevant for rolling CV or refit):
# For training obs (t > test_end), this is t in [test_end + 1, test_end + w]
feature_leak = (train_times >= test_end + 1) & (train_times <= test_end + w)
# Drop the leaking observations
purged = train_times[~(label_leak | feature_leak)]
return purged
For walk-forward CV, the train_times are all before test_start, so feature_leak is empty. Only label_leak applies.
For rolling CV or refit scenarios, both apply, and you should also drop observations in the embargo zone after test_end.
A Common Error: Purge on Both Sides
A common mistake in implementations: applying the same purge on both sides of the test fold. This is wrong.
The purge on the leading edge (before the test fold) is about label leakage: training observations whose labels extend into the test fold.
The “purge” on the trailing edge (after the test fold) is not a purge in the same sense. It is an embargo about feature leakage. Training observations whose features reach back into the test fold.
These are different mechanisms with different widths. The leading-edge purge width is (label horizon). The trailing-edge embargo width is (feature lookback). They should not be conflated.
If your implementation purges the same width on both sides, you are either over-purging on one side or under-purging on the other.
(AFML, §7.4.2-7.4.3.)
What If Labels and Features Have Different Windows?
The derivation above assumes all features have the same lookback window . In practice, features have different windows: 20-day volatility, 60-day momentum, 250-day moving average. The purge zone should be wide enough to cover the largest window.
Let be the largest feature lookback in the dataset. The embargo width should be at least . A conservative rule: use the maximum across all features.
A tighter rule: for each training observation, check whether its feature window (for the specific feature with the largest ) overlaps the test fold. This is more precise but computationally heavier.
A practical compromise: compute once and use it for all observations. This is fast and slightly conservative.
The Interaction with Sample Weights
The purge removes training observations. The sample weights (from the four-types article) assign weights to remaining observations. These are different operations:
- Purge: removes observations that would leak test information.
- Weights: down-weights observations that are redundant, outdated, or high-volatility.
A common confusion: thinking that sample weights can replace purge. They cannot. Weights adjust the importance of observations in the loss function. Purge removes observations entirely. An observation with weight zero still contributes to the training set’s distributional properties (e.g., the empirical distribution of features). A purged observation does not.
Use both. Purge first, then weight. The weights operate on the post-purge training set.
The Mathematical Expression
The complete purge rule in one expression:
In words: the purged training set is the set of training observations whose label windows and feature windows do not intersect the test fold.
For walk-forward CV (training is before test), the second condition is trivially satisfied for :
This is the rule: drop training observations in the days before the test fold starts.
The Embargo, Briefly
The trailing-edge case is covered in the next article. The rule:
For walk-forward CV (training is before test), only the first condition applies.
For rolling CV (training includes post-test data) or refit scenarios, both conditions apply, with a gap from to .
Common Errors
Five errors in implementing purged CV.
Error 1: Purge width based on calendar days, not label horizon.
If you have daily observations but labels computed over 5-day windows, the purge width is 5, not 1. The number of calendar days between observations is irrelevant; what matters is the label horizon in observation-index units.
Error 2: Forgetting the off-by-one in feature windows.
The feature window is , which has length but covers distinct observations. Don’t confuse (window length) with (number of observations). For consistency with label windows, use as the embargo width.
Error 3: Treating all features as having the same window.
If your dataset has features with 20-day and 250-day windows, the embargo must cover the 250-day window. Otherwise, the 250-day features leak on the trailing edge.
Error 4: Purging twice.
Some implementations purge, then re-purge on each test fold within a CV scheme. This double-purges, removing observations that should be kept. Apply the purge once per test fold boundary.
Error 5: Not purging when using triple-barrier labels.
Triple-barrier labels have a label horizon (the vertical barrier). The purge width should be , not the average holding period. Use the maximum label window.
The Bigger Picture
The purged CV derivation is a specific instance of a broader principle: avoid using future information to predict the past. In time-series ML, this principle manifests as:
- Temporal splits (walk-forward CV): don’t use future data to predict the past.
- Purging: don’t use training observations whose labels or features touch the test fold.
- Embargo: don’t use post-test training observations whose features reach back into the test fold.
- No look-ahead in features: don’t use features that depend on data after the prediction time.
Each of these rules addresses a specific leakage channel. Together, they form a complete defense against temporal contamination.
The math is simple. The discipline is hard. Most implementations get at least one of the rules wrong. A common audit question: “for each training observation, can you write down the exact data that was used to compute its features and labels, and verify that none of it overlaps with the test fold?” If you cannot, your CV is leaking.
(López de Prado, M., Advances in Financial Machine Learning, §7.4.2. ISBN 978-1119482086.)