QTQuant Terminal
D4-04D4·intermediate·~22 min

Walk-forward testing and out-of-sample discipline

backtestingwalk-forwardout-of-samplepurgingembargoholdout

▸ Pretest — guess, even if you don't know

You tune a strategy's lookback window to maximize Sharpe on 2010-2020 data, then report that same 2010-2020 Sharpe. What is the reported number?

In-sample vs out-of-sample

In-sample (IS): data used to make any choice — parameters, features, even which strategy idea to keep. Out-of-sample (OOS): data that influenced no decision. Only OOS performance estimates what happens next, because IS performance is contaminated by selection: you kept what looked good on it.

The subtlety from D4-01's failure mode #6: "choices" include soft ones. If you glanced at the full history before designing the strategy, the design is partly in-sample everywhere.

The walk-forward loop

Walk-forward analysis manufactures a sequence of honest OOS windows:

  1. Take the first training window; fit/tune there.
  2. Apply those frozen parameters to the test window immediately after.
  3. Slide forward by one test-window length; refit; repeat.
  4. Concatenate all test-window returns. That concatenation is your OOS track record.

With 10 periods, train width 4, test width 2 (rolling):

index:    0  1  2  3  4  5  6  7  8  9
split 1: [T  T  T  T][t  t]
split 2:       [T  T  T  T][t  t]
split 3:             [T  T  T  T][t  t]

Rolling windows (shown) keep the training width fixed — the fit adapts to regime change. Anchored windows always start at index 0, so training grows — more data, less adaptivity. Both are defensible; pick one before looking at results.

Purging and the embargo

Adjacent train/test windows still leak. Returns are autocorrelated, and labels often span several bars (a 5-day forward return computed at the end of training uses days inside the test window). López de Prado's fix: purge training samples whose information overlaps the test window, and add an embargo — a buffer gap of a few bars after the test window before training data resumes. Without it, the test set quietly grades the training set's homework.

The one-shot rule

Beyond walk-forward, keep a final holdout — the most recent year or two — that nothing touches until you are done. The rule: you get to open it once. Evaluate, accept the verdict. If you peek, dislike the result, tweak, and peek again, the holdout has become training data — you selected against it, and A2-05's multiple-testing arithmetic applies to it now. Touch it twice and it's contaminated.

Calibrate your expectations

Empirically, strategies that survive honest evaluation typically deliver OOS Sharpe around 30–70% of in-sample — Bailey and López de Prado's work on backtest overfitting shows the haircut is the norm, not the exception. And the final OOS test is one you can't simulate: paper trading and then live trading, where divergence from the backtest is the market grading your methodology. If your plan requires the IS number to hold up, you don't have a plan.

Try it

The bookkeeping of walk-forward is index arithmetic — get it exactly right once. Pure Python is fine here; no numpy needed.

▮ EXERCISE · d4-04-ex1

Implement walk_forward_splits(n, train, test): return a list of (train_start, train_end, test_start, test_end) tuples with exclusive ends, rolling forward by the test size. Start train_start at 0 and keep going while a full test window still fits (train_start + train + test <= n).

⧉ Review card
What makes a data point out-of-sample?
It influenced no decision: not parameter tuning, not feature selection, not the choice of which strategy idea to keep. Any data that shaped a choice is in-sample, and performance on it is biased upward by selection.
⧉ Review card
Describe the walk-forward loop.
Fit parameters on a training window, apply them frozen to the test window right after, slide forward by the test width, repeat. Concatenated test-window returns are the honest track record. Rolling = fixed train width; anchored = train always starts at 0 and grows.
⧉ Review card
What are purging and embargo, and why are they needed?
Purging removes training samples whose information (e.g., multi-day forward returns) overlaps the test window; the embargo adds a buffer gap after the test window. Both stop leakage through autocorrelation between adjacent train and test data (López de Prado).
⧉ Review card
What is the one-shot rule for the final holdout?
You may evaluate on the held-out period exactly once. Peek, tweak, and peek again, and you have selected against the holdout — it is now training data, and multiple-testing bias applies to it.
⧉ Review card
How much in-sample Sharpe typically survives out of sample?
Roughly 30-70 percent, per Bailey and López de Prado's backtest-overfitting research. Budget for the haircut; the final OOS test is paper and then live trading.

Map the concepts

Your generative activity: draw a concept map connecting these eight nodes — in-sample, out-of-sample, walk-forward, rolling window, anchored window, purging, embargo, final holdout. Label each edge with the relationship (for example, "protects against leakage into"). Add one edge back to A2-05 multiple testing showing where its bias enters.

Predict before the next lesson

D4-05 quantifies selection bias. Predict:

◈ Calibration check

Could you set up a walk-forward evaluation, explain purging and embargo, and state the one-shot rule for a holdout?

1 = guessing · 5 = could teach it

⏻ End of lesson

Mark it read to book its 5 review cards into your deck.

Sources & further reading