No lookahead
A result is a result only if the decision behind it could have been made at
the moment it is credited to. Otherwise it is a measurement of hindsight.
The failure has a formal name: leakage — "the introduction of information
about the target of a data mining problem which should not be legitimately
available to mine from" (Kaufman et al., see Grounding). In time-series work
it is look-ahead bias; the correctness property it violates is
point-in-time correctness; the technical-analysis artifact that causes it
is a repainting indicator. One defect, four vocabularies.
The shape
Two artifacts get confused because they are rendered from the same data:
|
Display artifact |
Decision artifact |
| Answers |
what happened |
what would I have done |
| May read |
the whole history |
only rows at or before t |
| Revised later |
legitimately, yes |
never |
A chart, a labelled dataset, an annotated log, a resolved ticket, and a
post-hoc report are all display artifacts. They are drawn after, and are
allowed to move a marker back onto the moment the thing "really" happened.
Feed one into a decision evaluation and the decision is credited with
knowledge it did not have.
The leaked value can be entirely real and the leak still total. The leak
is in when the decision became available, not in the number's accuracy.
A price, a label, or a document can be genuine at time t while the
instruction to act on it does not exist until t + k.
Must always
- Name the cutoff before the first measurement. For every input, record
two timestamps: what it is about, and when it became knowable. Only
the second one may gate a decision. If a source cannot tell you the
second, it is a display artifact until proven otherwise.
- Run the truncation test. Recompute the decision from only the data
available at time
t, and compare it to what the full-history computation
says at that same t. Any disagreement at any t is a leak, and the
disagreeing indices name it. Ten cut points expose a gross leak in
seconds; a replay at every step is the proof.
- Separate knowability from actionability. The truncation test proves the
input existed at
t. It does not prove the action was executable at t.
Name the execution cutoff too: the earliest moment the decision could have
reached the world, and the price, latency, or capacity it would have met
there. A decision that observes a bar's close and fills at that same close
is zero-latency, and zero latency is a form of hindsight the prefix test
cannot see.
- Bind the test to the artifact that produced the number, not to a
library function beside it. A causality test that guards
compute()
proves nothing about a script that imports compute() and passes it a
different mode. The gate belongs on the path the reported number came out
of — including throwaway analysis scripts, notebooks, and heredocs.
- Treat an implausibly good result as a bug report, not an achievement.
Fix the implausibility ceiling from the domain's own known best before
running, and stop on any of: a score above what the best known
practitioner achieves; an error near zero; a monotone result with no bad
period across a span long enough to contain one; a metric that improves
when an input that should be irrelevant is added; a parameter sweep where
every cell wins. The stronger the number, the earlier you stop. Stop and
find the mechanism — leakage, an inert axis that never varied the output, a
frozen constant, or a coding error — before reporting the number at all.
Report the number of configurations tried alongside the best one; without
it, a reader cannot tell skill from selection.
- Re-run the cutoff check after any change to what an input reads — a
new normalization, a rolling window, a resample, a join, a smoothing term,
a re-label. Each can silently widen the window a value is computed over.
- Reject a negative offset. Any shift, lag, or window parameter exposed
to a caller must refuse values that reach forward. An unvalidated
shift(k) is a lookahead switch with no label on it.
Must never
- Score a decision against a label that was assigned with knowledge of the
outcome.
- Accept "it only reads past data" from a grep, a variable name, a
docstring, or a commit message. Causality is a property of the output,
measured. It is not a property of the source, read.
- Let a display mode and a decision mode share a return type. If one
function can emit both, a caller will eventually pass the wrong one and
nothing will say so. Make the display result structurally unable to reach
the scoring path.
- Report a result whose sign depends on an assumption that was never varied.
If one unswept constant flips the conclusion, that constant is the finding.
- Publish a number produced before the truncation test ran. On discovery,
void the old numbers in place — move them to a
voided/ path and
retract the headline — rather than deleting them, so the record shows what
was believed and for how long.
The cheap test, in general form
Grounding
- Leakage, formalized, with the legitimacy-in-time condition and the
learn-predict separation that avoids it → Shachar Kaufman, Saharon Rosset,
Claudia Perlich, Ori Stitelman, "Leakage in Data Mining: Formulation,
Detection, and Avoidance," ACM TKDD 6(4), Article 15 (2012)
https://doi.org/10.1145/2382577.2382579. This is the paper that names
this exact failure; its KDD-Cup and INFORMS case studies are both
"implausibly good result turned out to be a leak."
- Evaluating only from an origin that moves forward through the data →
Leonard J. Tashman, "Out-of-sample tests of forecasting accuracy: an
analysis and review," International Journal of Forecasting 16(4),
437-450 (2000) https://doi.org/10.1016/S0169-2070(00)00065-0. Rolling
origin is the truncation test as a standard evaluation design.
- Why a too-good backtest is evidence about the search process rather than
the strategy, and why the count of configurations tried must be reported →
David H. Bailey, Jonathan M. Borwein, Marcos López de Prado, Qiji Jim Zhu,
"Pseudo-Mathematics and Financial Charlatanism: The Effects of Backtest
Overfitting on Out-of-Sample Performance," Notices of the AMS 61(5),
458-471 (2014) https://www.ams.org/notices/201405/rnoti-p458.pdf.
- Purged cross-validation and embargo, for label overlap leaking across a
split → Marcos López de Prado, Advances in Financial Machine Learning,
Wiley (2018), ch. 7 and ch. 11.
- "Repainting," for a display artifact that moves a marker backward:
no known peer-reviewed prior art. It is vendor and forum vocabulary
only. Cite the mechanism, not the word.
Related: [[principle-assert-invariants-not-last-bug]] (the truncation test is
the class invariant, not a patch for the leak you found);
[[principle-explicit-errors]] (a suppressed or unvalidated offset is how the
leak gets in silently); [[principle-report-the-disqualifier]] (what the
report must disclose once the evaluation is clean).
1---2name: principle-no-lookahead3description: Apply to any evaluation, backtest, benchmark, grader, or training run over data with a time axis or an ordered decision sequence. Score a decision only against inputs that existed before that decision was made. Covers what the field calls look-ahead bias, target leakage, point-in-time correctness, and repainting. Reach for it on: a backtest or walk-forward run, a feature computed over a full history, a retrieval or agent eval, a label assigned after the outcome was known, or any result that looks too good.4---56# No lookahead78A result is a result only if the decision behind it could have been made at9the moment it is credited to. Otherwise it is a measurement of hindsight.1011The failure has a formal name: **leakage** — "the introduction of information12about the target of a data mining problem which should not be legitimately13available to mine from" (Kaufman et al., see Grounding). In time-series work14it is **look-ahead bias**; the correctness property it violates is15**point-in-time correctness**; the technical-analysis artifact that causes it16is a **repainting** indicator. One defect, four vocabularies.1718## The shape1920Two artifacts get confused because they are rendered from the same data:2122| | Display artifact | Decision artifact |23|---|---|---|24| Answers | what happened | what would I have done |25| May read | the whole history | only rows at or before `t` |26| Revised later | legitimately, yes | never |2728A chart, a labelled dataset, an annotated log, a resolved ticket, and a29post-hoc report are all display artifacts. They are drawn *after*, and are30allowed to move a marker back onto the moment the thing "really" happened.31Feed one into a decision evaluation and the decision is credited with32knowledge it did not have.3334**The leaked value can be entirely real and the leak still total.** The leak35is in *when the decision became available*, not in the number's accuracy.36A price, a label, or a document can be genuine at time `t` while the37instruction to act on it does not exist until `t + k`.3839## Must always4041- **Name the cutoff before the first measurement.** For every input, record42 two timestamps: what it is *about*, and when it became *knowable*. Only43 the second one may gate a decision. If a source cannot tell you the44 second, it is a display artifact until proven otherwise.45- **Run the truncation test.** Recompute the decision from only the data46 available at time `t`, and compare it to what the full-history computation47 says at that same `t`. Any disagreement at any `t` is a leak, and the48 disagreeing indices name it. Ten cut points expose a gross leak in49 seconds; a replay at every step is the proof.50- **Separate knowability from actionability.** The truncation test proves the51 input existed at `t`. It does not prove the action was executable at `t`.52 Name the execution cutoff too: the earliest moment the decision could have53 reached the world, and the price, latency, or capacity it would have met54 there. A decision that observes a bar's close and fills at that same close55 is zero-latency, and zero latency is a form of hindsight the prefix test56 cannot see.57- **Bind the test to the artifact that produced the number**, not to a58 library function beside it. A causality test that guards `compute()`59 proves nothing about a script that imports `compute()` and passes it a60 different mode. The gate belongs on the path the reported number came out61 of — including throwaway analysis scripts, notebooks, and heredocs.62- **Treat an implausibly good result as a bug report, not an achievement.**63 Fix the implausibility ceiling from the domain's own known best *before*64 running, and stop on any of: a score above what the best known65 practitioner achieves; an error near zero; a monotone result with no bad66 period across a span long enough to contain one; a metric that improves67 when an input that should be irrelevant is added; a parameter sweep where68 every cell wins. The stronger the number, the earlier you stop. Stop and69 find the mechanism — leakage, an inert axis that never varied the output, a70 frozen constant, or a coding error — before reporting the number at all.71 Report the number of configurations tried alongside the best one; without72 it, a reader cannot tell skill from selection.73- **Re-run the cutoff check after any change to what an input reads** — a74 new normalization, a rolling window, a resample, a join, a smoothing term,75 a re-label. Each can silently widen the window a value is computed over.76- **Reject a negative offset.** Any shift, lag, or window parameter exposed77 to a caller must refuse values that reach forward. An unvalidated78 `shift(k)` is a lookahead switch with no label on it.7980## Must never8182- Score a decision against a label that was assigned with knowledge of the83 outcome.84- Accept "it only reads past data" from a grep, a variable name, a85 docstring, or a commit message. Causality is a property of the output,86 measured. It is not a property of the source, read.87- Let a display mode and a decision mode share a return type. If one88 function can emit both, a caller will eventually pass the wrong one and89 nothing will say so. Make the display result structurally unable to reach90 the scoring path.91- Report a result whose sign depends on an assumption that was never varied.92 If one unswept constant flips the conclusion, that constant is the finding.93- Publish a number produced before the truncation test ran. On discovery,94 void the old numbers **in place** — move them to a `voided/` path and95 retract the headline — rather than deleting them, so the record shows what96 was believed and for how long.9798## The cheap test, in general form99100## Grounding101102- Leakage, formalized, with the legitimacy-in-time condition and the103 learn-predict separation that avoids it → Shachar Kaufman, Saharon Rosset,104 Claudia Perlich, Ori Stitelman, "Leakage in Data Mining: Formulation,105 Detection, and Avoidance," *ACM TKDD* 6(4), Article 15 (2012)106 <https://doi.org/10.1145/2382577.2382579>. This is the paper that names107 this exact failure; its KDD-Cup and INFORMS case studies are both108 "implausibly good result turned out to be a leak."109- Evaluating only from an origin that moves forward through the data →110 Leonard J. Tashman, "Out-of-sample tests of forecasting accuracy: an111 analysis and review," *International Journal of Forecasting* 16(4),112 437-450 (2000) <https://doi.org/10.1016/S0169-2070(00)00065-0>. Rolling113 origin is the truncation test as a standard evaluation design.114- Why a too-good backtest is evidence about the search process rather than115 the strategy, and why the count of configurations tried must be reported →116 David H. Bailey, Jonathan M. Borwein, Marcos López de Prado, Qiji Jim Zhu,117 "Pseudo-Mathematics and Financial Charlatanism: The Effects of Backtest118 Overfitting on Out-of-Sample Performance," *Notices of the AMS* 61(5),119 458-471 (2014) <https://www.ams.org/notices/201405/rnoti-p458.pdf>.120- Purged cross-validation and embargo, for label overlap leaking across a121 split → Marcos López de Prado, *Advances in Financial Machine Learning*,122 Wiley (2018), ch. 7 and ch. 11.123- "Repainting," for a display artifact that moves a marker backward:124 **no known peer-reviewed prior art.** It is vendor and forum vocabulary125 only. Cite the mechanism, not the word.126127Related: [[principle-assert-invariants-not-last-bug]] (the truncation test is128the class invariant, not a patch for the leak you found);129[[principle-explicit-errors]] (a suppressed or unvalidated offset is how the130leak gets in silently); [[principle-report-the-disqualifier]] (what the131report must disclose once the evaluation is clean).