Skip to content

The Embargo Effect

Purge handles the leading edge. Embargo handles the trailing edge. Together they form the complete defense against feature-label overlap. Skip the embargo and your CV is still lying.

11 of 12 ~13 min

The Embargo Effect: Why Purging Alone Is Not Enough

Purging removes training observations whose labels extend into the test fold. It handles the leading-edge leakage: training samples that are contaminated because their label windows reach forward into the test period. Purging is well understood. Most implementations do it.

Embargo is the other half. It handles the trailing-edge leakage: training observations (after the test fold) whose features reach backward into the test period. Without embargo, the model trained on post-test data has features that were computed using test-period data. The labels are clean. The features are not. The CV score is biased upward. The strategy will underperform live.

This article covers why embargo is necessary, how to size it, and how it interacts with purging. The math is the same as the purge derivation (previous article). The discipline is different: most practitioners skip embargo because the leakage is invisible.

(López de Prado, M., Advances in Financial Machine Learning, §7.4.3. ISBN 978-1119482086.)

The Trailing-Edge Leakage

Picture the time axis. Test fold is from TsT_s to TeT_e. Training data exists both before TsT_s and after TeT_e.

Consider a training observation at time tt, where t>Tet > T_e. Its features are computed over [t−w,t−1][t-w, t-1]. If t−w<Tet - w < T_e, the feature window reaches back into the test fold. The features are contaminated.

For example, suppose the feature is a 20-day moving average. At time t=Te+5t = T_e + 5, the feature window is [Te−15,Te+4][T_e - 15, T_e + 4]. This window includes TeT_e to Te+4T_e + 4 (after the test fold) and Te−15T_e - 15 to Te−1T_e - 1 (just before and at the test fold start). Wait, TeT_e is the end of the test fold, so the window [Te−15,Te+4][T_e - 15, T_e + 4] includes [Te−15,Te][T_e - 15, T_e] which overlaps with the test fold [Ts,Te][T_s, T_e].

The 5 days from Te−15T_e - 15 to Te−1T_e - 1 are within the test fold (assuming Ts≤Te−15T_s \leq T_e - 15, which is true if the test fold is at least 16 days). The features at t=Te+5t = T_e + 5 are contaminated.

The label at time t=Te+5t = T_e + 5 is fine (its label window is [Te+5,Te+5+h][T_e + 5, T_e + 5 + h], which does not overlap the test fold). Only the features leak.

This is the trailing-edge leakage. Purge does not catch it (purge only handles leading-edge label leakage). Embargo does.

The Embargo Rule

Drop training observations in the interval:

[Te+1,Te+w][T_e + 1, T_e + w]

where ww is the largest feature lookback in the dataset. The embargo width is ww.

A subtlety: the embargo should be applied to observations where their feature window would reach back into the test fold. The strict rule is: drop tt if t−w≤Tet - w \leq T_e. For training observations t>Tet > T_e, this is t≤Te+wt \leq T_e + w. So the embargo interval is [Te+1,Te+w][T_e + 1, T_e + w].

The width is exactly ww, the same as the feature lookback.

(AFML, §7.4.3.)

A Concrete Example

Setup: 60-day momentum feature, 5-day holding period, daily observations. Test fold is 252 days (one trading year).

The label at time tt depends on data in [t,t+5][t, t+5]. The feature at time tt depends on data in [t−60,t−1][t-60, t-1]. The test fold is [Ts,Te][T_s, T_e] with Te−Ts=252T_e - T_s = 252.

Leading edge: training observations in [Ts−5,Ts][T_s - 5, T_s] have label windows that extend into the test fold. These are purged. The purge width is 5.

Trailing edge: training observations in [Te+1,Te+60][T_e + 1, T_e + 60] have feature windows that reach back into the test fold. These are embargoed. The embargo width is 60.

Total “lost” training observations: 5+60=655 + 60 = 65. For a 252-day test fold, that’s 26% of the test fold size. The training set is reduced by 65 days.

For a 10-year dataset (about 2,520 trading days), the loss is 65/2,520 = 2.6%. Small. But the leakage it prevents is meaningful: without embargo, the post-test training observations have features contaminated by test-period data, and the CV score is biased upward by an unknown amount.

When Embargo Matters Most

Three scenarios.

Scenario 1: Long feature windows.

If your features include 250-day moving averages, the embargo is 250 days. This is a substantial fraction of a typical test fold (252 days). The embargo reduces training data by ~50% on the trailing edge.

Scenario 2: Regime transitions.

If the test fold contains a regime change (e.g., a financial crisis), the post-test data has features computed using the crisis-period data. The model trained on this data is contaminated by crisis patterns. The embargo prevents this.

Scenario 3: Refit scenarios.

If you refit the model after the test fold ends, the new training set includes the embargo zone. Without embargo, the new model has features that “remember” the test fold. The embargo prevents the model from implicitly memorizing the test period.

In all three cases, the embargo is necessary for honest CV.

When Embargo Is Less Important

Three scenarios where embargo has smaller impact.

Scenario 1: Walk-forward CV with no retraining within the test fold.

If you train once on [Ts−h−training size,Ts−h−1][T_s - h - \text{training size}, T_s - h - 1] and evaluate on [Ts,Te][T_s, T_e], there is no post-test training data. The embargo does not apply.

This is the typical walk-forward setup. The embargo matters mainly for rolling CV and refit scenarios.

Scenario 2: Short feature windows.

If your largest feature window is 5 days, the embargo is 5 days. The data loss is minimal.

Scenario 3: Long test folds.

If the test fold is 1000 days, the 60-day embargo is 6% of the test fold size. Smaller relative impact.

The Interaction with Refitting

The most common case where embargo matters: refit scenarios.

Imagine you train a model on data ending at Ts−1T_s - 1, evaluate on [Ts,Te][T_s, T_e], then refit on data ending at Te+embargoT_e + \text{embargo}. The refit training set has data in [Te+1,Te+embargo][T_e + 1, T_e + \text{embargo}] whose features reach back into [Ts,Te][T_s, T_e]. The refit model is contaminated.

The fix: skip the embargo zone in the refit training set. The refit model trains on [Ts−h−training size,Ts−h−1]∪[Te+w+1,Te+new training end][T_s - h - \text{training size}, T_s - h - 1] \cup [T_e + w + 1, T_e + \text{new training end}], with the embargo zone excluded.

In code:

def embargo_post_test(train_times, test_end, w):
    """Drop training observations in [test_end + 1, test_end + w]."""
    embargo_start = test_end + 1
    embargo_end = test_end + w
    return train_times[(train_times < embargo_start) | (train_times > embargo_end)]

A common error: forgetting to apply embargo after each test fold in a multi-fold CV scheme. If you have 5 test folds, you have 5 embargo zones. Each must be excluded from the corresponding training set.

The Asymmetry Between Purge and Embargo

The two mechanisms are similar in form but different in spirit.

Purge: addresses label leakage. The label at time tt depends on data in [t,t+h][t, t+h]. If this window intersects the test fold, the label is contaminated. The contamination is about the outcome of the training observation.

Embargo: addresses feature leakage. The features at time tt depend on data in [t−w,t−1][t-w, t-1]. If this window intersects the test fold, the features are contaminated. The contamination is about the input to the training observation.

Both are real. Both bias the CV score upward. Both must be addressed.

The asymmetry is in the direction. Purge removes pre-test training data. Embargo removes post-test training data. They are mirror images of each other, but the math is different because the label window extends forward (to [t,t+h][t, t+h]) and the feature window extends backward (to [t−w,t−1][t-w, t-1]).

The Embargo Width: How Big Should It Be?

The strict rule: the embargo width should be at least as large as the largest feature lookback wmax⁡w_{\max} in the dataset.

A useful diagnostic: list all features in your dataset with their lookback windows. The maximum is wmax⁡w_{\max}. Set the embargo to wmax⁡w_{\max}.

A more precise rule: per-feature embargo. For each feature, check if its window reaches back into the test fold. If so, drop the observation. This is computationally heavier but more precise.

A heuristic for high-frequency strategies: use a percentage of the test fold. For test folds of 252 days, an embargo of 60 days is 24%. For test folds of 21 days, an embargo of 5 days is 24%. The percentage approach scales with the test fold size.

(AFML, §7.4.3.)

The Percent-of-Test-Fold Heuristic

A simpler rule: set the embargo to a percentage of the test fold size, e.g., 1% or 5%. This is a heuristic that scales with the test fold size.

For a 252-day test fold with 1% embargo, the embargo is 2.52 days, rounded to 3 days. This is shorter than a typical 60-day momentum feature window. The heuristic is too narrow for many use cases.

For a 5% embargo on a 252-day test fold, the embargo is 12.6 days, rounded to 13 days. Still shorter than 60 days.

The percentage heuristic works for high-frequency strategies with short feature windows. It fails for swing or position strategies with long windows.

A safer rule: use the maximum of (percentage of test fold, wmax⁡w_{\max}).

The Embargo and Concurrency

A further refinement: when many observations have overlapping label windows, the unique information content of post-test training data is small. The embargo should account for this.

The same concurrency adjustment as in purge (previous article):

adjusted embargo width=c⋅w\text{adjusted embargo width} = c \cdot w

where cc is the average concurrency of labels. For triple-barrier labels with h=5h = 5 and daily observations, cc is about 5. For w=60w = 60, the adjusted embargo is 300 days. This is more aggressive than the strict ww.

The justification: if each post-test observation’s label overlaps with cc others, the compound feature leakage across all cc labels is larger than the simple ww. The adjusted embargo accounts for this.

(AFML, §4.4 and §7.4.3.)

The Embargo and CPCV

In CPCV (Combinatorial Purged Cross-Validation), each path through the data has multiple test folds. The embargo must be applied at each test-fold boundary.

A useful mental model: for each test fold [Ts(k),Te(k)][T_s^{(k)}, T_e^{(k)}], apply purge in [Ts(k)−h,Ts(k)][T_s^{(k)} - h, T_s^{(k)}] and embargo in [Te(k)+1,Te(k)+w][T_e^{(k)} + 1, T_e^{(k)} + w]. Union all the embargo zones across all test folds. The remaining times are the training set.

For a CPCV with 6 groups and 2-test-group paths, there are 15 paths, each with 2 test folds. The union of embargo zones can cover a substantial fraction of the data.

A practical consideration: some observations may be embargoed by multiple test folds. This is fine; they are simply not in the training set. The training set is smaller, but it is clean.

(AFML, §12.3.2.)

A Common Error: Embargo on Both Sides

A common mistake: applying the embargo on both sides of the test fold (before and after). This is wrong.

The “before” side is handled by purge, not embargo. Purge addresses label leakage (training observations whose labels extend into the test fold). Embargo on the “before” side would address the same leakage with a different mechanism, but it is redundant.

The embargo is specifically about post-test data: training observations whose features reach back into the test fold. Embargo on the pre-test side does not make sense.

If your implementation embargoes both sides, you are over-aggressive: you are removing observations that should be kept.

A Common Error: Forgetting Embargo in Refit

A common mistake: applying purge but not embargo when refitting the model after the test fold ends.

The thinking: “the test fold is over, the embargo is irrelevant.” But the embargo is about the post-test training data, which the next refit will use. If you skip the embargo in the refit, the new model has features contaminated by test-period data.

The fix: always apply embargo in refit scenarios. The embargo zone is the same as in the initial training: [Te+1,Te+w][T_e + 1, T_e + w].

The Embargo and Online Learning

In online learning (covered in a later article), the model is updated continuously as new data arrives. The embargo is automatically handled: the post-test data is not used for training until enough time has passed that the features no longer reach back into the test period.

In practice, online learning systems maintain a sliding window of training data. The window is at least w+hw + h wide to ensure no temporal leakage. The “embargo” is implicit in the window structure.

A Useful Diagnostic

Compute the CV score with and without embargo. The difference is your estimate of the leakage bias.

If the difference is small (e.g., Sharpe of 1.5 with embargo vs 1.55 without), the leakage is minimal. The strategy is probably robust.

If the difference is large (e.g., Sharpe of 1.0 with embargo vs 2.0 without), the leakage is substantial. The strategy’s “true” CV score is much lower than the naive one. Without embargo, you would deploy a strategy that underperforms live by 50% or more.

Run this diagnostic on every CV scheme you use. If the gap is large, fix the embargo. If the gap is small, the embargo is less critical for this dataset.

The Embargo in Walk-Forward vs Rolling CV

The embargo’s importance depends on the CV scheme.

Walk-forward CV (expanding or rolling training window, single forward test): the test fold is at the end of the sample. There is no post-test training data. The embargo does not apply.

Rolling CV with periodic refits: the model is refit at each step, using data up to the current date. The post-test training data (after the previous test fold) is used to fit the next iteration. The embargo applies: skip the post-test zone where features reach back into the previous test fold.

Combinatorial Purged CV (CPCV): multiple overlapping test folds, each requiring its own embargo. The union of all embargo zones can cover a substantial fraction of the data. Compute the embargo for each test fold separately and union them.

A common implementation pattern:

def cv_with_embargo(data, test_folds, h, w):
    """Apply purge and embargo for each test fold."""
    embargoed_indices = set()
    for test_start, test_end in test_folds:
        # Purge: leading-edge training observations
        purge_start = test_start - h
        purge_end = test_start
        embargoed_indices.update(range(purge_start, purge_end))
        
        # Embargo: trailing-edge training observations
        embargo_start = test_end + 1
        embargo_end = test_end + w
        embargoed_indices.update(range(embargo_start, embargo_end))
    
    # Mask out embargoed observations
    mask = np.ones(len(data), dtype=bool)
    mask[list(embargoed_indices)] = False
    return data[mask]

The function returns the data with embargoed indices masked out.

The Leakage Budget

A useful diagnostic: estimate the leakage that would occur without embargo.

For a 252-day test fold with h=5h = 5 and w=60w = 60:

  • Purge width: 5 days.
  • Embargo width: 60 days.
  • Total “lost” observations: 65.

Without embargo, the 60 post-test observations would have features computed using test-period data. The leakage is bounded by the correlation between the contaminated features and the test-fold outcomes.

In practice, the leakage is small for stable feature windows (e.g., 20-day momentum) and large for long windows (e.g., 250-day moving average). For a 250-day moving average, the embargo of 250 days prevents substantial leakage.

A useful rule of thumb: the leakage without embargo is proportional to the feature window size ww. Larger ww, larger leakage, more important embargo.

(AFML, §7.4.3.)

The Embargo and Sample Weights

The embargo removes training observations. The sample weights (from the sample weights article) down-weight the remaining observations. They are complementary operations.

A typical pipeline:

  1. Apply embargo to remove leaky observations.
  2. Apply uniqueness weights to handle overlapping labels.
  3. Apply time decay to handle regime drift.
  4. Apply volatility scaling to handle absolute-return heterogeneity.

The order matters: embargo first (cleanest data), then uniqueness (handles label overlap), then time decay and volatility scaling (handle regime and absolute-return issues).

The post-embargo training set is smaller than the original. The post-weight training set has the same size but different observation weights. Both are necessary for honest ML on financial data.

(AFML, Ch. 4 and Ch. 7.)

What Embargo Buys You

An honest CV score. Without embargo, the CV score is biased upward by feature leakage. With embargo, the score is closer to live performance.

The improvement is typically 5-20% in Sharpe (the unbiased Sharpe is lower than the biased one). For a strategy that appears to have Sharpe 2.0 without embargo, the embargoed CV might show Sharpe 1.7. The 2.0 was optimistic; the 1.7 is closer to reality.

The cost is data: the embargo removes observations from the training set. For most pipelines, the cost is small (a few percent of the data). For pipelines with very long feature windows (e.g., 250-day), the cost is larger (up to 50% of the test fold size on the trailing edge).

The trade-off: more honest CV (with embargo) vs. more data (without embargo). For most pipelines, the honest CV is worth the data cost. The first time your embargoed CV disagrees with your non-embargoed CV, you will be glad you implemented it.

(López de Prado, M., Advances in Financial Machine Learning, §7.4.3. ISBN 978-1119482086.)