Skip to content

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.

7 of 12 ~12 min

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 hh, the feature lookback ww, 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 tt. Each observation has:

  • A label window [t,t+h][t, t+h]: the time interval over which the label is computed (e.g., the holding period for the trade).
  • A feature window [t−w,t−1][t-w, t-1]: the time interval over which the features are computed (e.g., the rolling lookback for momentum).

The label at time tt depends on data in [t,t+h][t, t+h]. The features at time tt depend on data in [t−w,t−1][t-w, t-1].

The test fold is a contiguous interval [Ts,Te][T_s, T_e] of times. The training set is everything else.

We want to know: for which training observations tt 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 tt depends on data in [t,t+h][t, t+h]. If this interval intersects the test fold [Ts,Te][T_s, T_e], 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 tt depend on data in [t−w,t−1][t-w, t-1]. 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 tt such that [t,t+h]∩[Ts,Te]≠∅[t, t+h] \cap [T_s, T_e] \neq \emptyset.

Two intervals [a,b][a, b] and [c,d][c, d] intersect if and only if a≤da \leq d and c≤bc \leq b. Equivalently, the maximum of the left endpoints is less than or equal to the minimum of the right endpoints.

Applying this:

[t,t+h]∩[Ts,Te]≠∅  ⟺  max⁡(t,Ts)≤min⁡(t+h,Te)[t, t+h] \cap [T_s, T_e] \neq \emptyset \iff \max(t, T_s) \leq \min(t+h, T_e)

For training observations (t<Tst < T_s, since training is before the test fold), this simplifies to:

t≤Ts≤t+h  ⟺  Ts−h≤t≤Tst \leq T_s \leq t+h \iff T_s - h \leq t \leq T_s

So label leakage affects training observations in [Ts−h,Ts][T_s - h, T_s].

The width of the label-leakage interval is exactly hh (the label horizon).

(AFML, Eq. 7.1.)

Channel 2: Feature Leakage in Detail

We want to find all tt such that [t−w,t−1]∩[Ts,Te]≠∅[t-w, t-1] \cap [T_s, T_e] \neq \emptyset.

Same logic:

[t−w,t−1]∩[Ts,Te]≠∅  ⟺  max⁡(t−w,Ts)≤min⁡(t−1,Te)[t-w, t-1] \cap [T_s, T_e] \neq \emptyset \iff \max(t-w, T_s) \leq \min(t-1, T_e)

For training observations (t<Tst < T_s):

t−w≤Ts≤t−1  ⟺  Ts+1≤t≤Ts+wt-w \leq T_s \leq t-1 \iff T_s + 1 \leq t \leq T_s + w

So feature leakage affects training observations in [Ts+1,Ts+w][T_s + 1, T_s + w].

The width of the feature-leakage interval is w−1w - 1 (slightly less than the feature window, because the upper endpoint of the feature window is t−1t-1, not tt).

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:

[Ts−h,Ts]∪[Ts+1,Ts+w][T_s - h, T_s] \cup [T_s + 1, T_s + w]

But wait: the training set is strictly before the test fold in walk-forward CV. The interval [Ts+1,Ts+w][T_s + 1, T_s + w] is after the test fold, so it is not in the training set anyway. The relevant leakage interval is just:

[Ts−h,Ts][T_s - h, T_s]

So the purge width for training observations is hh, the label horizon.

This is the rule: drop training observations in the hh days immediately preceding the test fold.

(AFML, §7.4.2.)

The Embargo Direction

The interval [Ts+1,Ts+w][T_s + 1, T_s + w] 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 w−1w - 1 (or ww, 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 hh days on the leading edge. In rolling CV or refit scenarios, also embargo ww days on the trailing edge.

A Geometric Visualization

Picture the time axis. Test fold is from TsT_s to TeT_e.

        |--h--|                              |---w---|
--------[-----]--------------------------------[-----]---------
       T_s-h   T_s                          T_e+1   T_e+w
        PURGE                                 EMBARGO

The purge zone is the hh days immediately before the test fold. The embargo zone is the ww 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 Ts−h−1T_s - h - 1 (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 Ts−h−1T_s - h - 1 to Te+w+1T_e + w + 1 (approximately, with overlap depending on rolling window size).

The Special Case of Triple-Barrier Labels

The derivation above assumes the label window is exactly [t,t+h][t, t+h]. 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 hh days. The dynamic barriers may be hit sooner. The label window is [t,thit][t, t_{\text{hit}}] where thit≤t+ht_{\text{hit}} \leq t + h.

For the purge derivation, use the maximum possible label window, which is hh. 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 h=5h = 5 instead of the average thit−t=1.5t_{\text{hit}} - t = 1.5. 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 cc concurrent labels per observation (i.e., each label overlaps with c−1c - 1 others on average), the effective label horizon is roughly c⋅hc \cdot h. The purge width should be scaled by cc:

adjusted purge width=c⋅h\text{adjusted purge width} = c \cdot h

This is a heuristic, not a strict derivation. But it captures the intuition that overlapping labels create compound leakage that a simple hh-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 hh (label horizon). The trailing-edge embargo width is ww (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 ww. 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 wmax⁡w_{\max} be the largest feature lookback in the dataset. The embargo width should be at least wmax⁡w_{\max}. 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 ww) overlaps the test fold. This is more precise but computationally heavier.

A practical compromise: compute wmax⁡w_{\max} 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:

Tpurged={t∈Ttrain:[t,t+h]∩[Ts,Te]=∅ and [t−w,t−1]∩[Ts,Te]=∅}\mathcal{T}_{\text{purged}} = \{ t \in \mathcal{T}_{\text{train}} : [t, t+h] \cap [T_s, T_e] = \emptyset \text{ and } [t-w, t-1] \cap [T_s, T_e] = \emptyset \}

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 t<Tst < T_s:

Tpurged={t∈Ttrain:t<Ts−h}\mathcal{T}_{\text{purged}} = \{ t \in \mathcal{T}_{\text{train}} : t < T_s - h \}

This is the rule: drop training observations in the hh days before the test fold starts.

The Embargo, Briefly

The trailing-edge case is covered in the next article. The rule:

Tpurged, embargoed={t∈Ttrain:t<Ts−h or t>Te+w}\mathcal{T}_{\text{purged, embargoed}} = \{ t \in \mathcal{T}_{\text{train}} : t < T_s - h \text{ or } t > T_e + w \}

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 Ts−h−1T_s - h - 1 to Te+w+1T_e + w + 1.

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 [t−w,t−1][t-w, t-1], which has length ww but covers ww distinct observations. Don’t confuse ww (window length) with w−1w - 1 (number of observations). For consistency with label windows, use ww 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 hh (the vertical barrier). The purge width should be hh, 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.)