Four places look-ahead bias leaks into a chart replay
Cropping the candles at the decision point closes one of them. The other three are a derived indicator, the record the question travels in, and the next question.
In short
- Cropping the candles at the decision point is the obvious leak, and the easiest one to close.
- Three harder ones remain: a derived indicator computed over the whole session, the answer travelling inside the record the question ships in, and the next question overlapping the last one's outcome.
- The fourth is the one nobody plans for — two questions from the same session an hour apart, where the later chart already contains the earlier answer.
Ask someone how they would build a chart-reading quiz from historical data and you will usually get the right first answer: cut the chart off at the decision point, hide what comes after. That is correct, and it is the least interesting part of the problem.
Look-ahead bias — anything on screen that was computed with knowledge of what happened next — has more places to hide than the candles. When we built our own question bank we found four layers, and the visible chart was the only one that was obvious.
1. The visible chart — the one everybody closes#
The rule here is unglamorous: everything the player sees is computed only from bars up to and including the decision point.
The one decision worth naming is that the chart is cropped, not masked. A masked chart is drawn from the whole session and then covered up, which means the future is present in the object — in the axis range, in the scaling, in whatever the renderer did before the mask went on. A cropped chart never contained it.
A reader will not tell those apart — unless they know to look at the axis, which is where the difference surfaces.
Drawn from the whole session, then covered. The axis was fitted to a peak the reader is not shown, so the visible line sits low and the bracket marks headroom that exists for nothing you can see.
Never drawn past the decision point. The axis fits the visible bars alone, so the same path fills the panel.
The visible consequence is that the session high and low marked on the chart are the high and low so far, not the day's final ones. If they were the day's, the marker would quietly be telling you how far price still had to travel.
2. The derived series — the one that actually catches people#
A chart is raw bars. The lines drawn on it are not, and this is where the leak usually happens, because the code that leaks looks perfectly reasonable.
We show VWAP, the most-watched intraday line. Our generator computes it across the whole session's bars and then slices out the visible part. Read quickly, that is exactly the mistake this article is about — the computation touched future bars.
It is safe, and the reason is worth stating precisely, because it is the rule that decides every other case. VWAP is cumulative: its value at bar i is a running total over bars 0 through i and mathematically cannot depend on bar i+1. Computing the full series and slicing gives the identical number to computing it on the truncated series. Nothing crossed the line.
ATR does not have that property. It is a mean over a trailing window, so a naive implementation computed on the full session and then indexed would pull in bars from the far side of the decision point. In our generator ATR is called on an explicitly truncated slice — the bars up to the cut, and nothing else — and that difference in one line of code is the entire distinction.
So the rule we hold ourselves to is not "never compute over the whole array". It is: a value at index i may only be a function of bars at or before i. Cumulative series satisfy that by construction. Window statistics, extremes, and anything normalised by a full-period figure do not, and have to be truncated before they are used.
The same test applies to the descriptive tags each question carries — which side of VWAP price sits on, where it is inside the day's range so far, whether it broke the opening range. Those are shipped with the question and are the sort of metadata that gets computed late, at packaging time, when the outcome is sitting right there in the same record. They are computed at the decision point instead.
3. The record the question travels in — not on the chart at all#
Here is the part that surprised us, and the reason we stopped treating look-ahead as a charting problem.
Our generated questions deliberately keep more than they show. Alongside the visible bars, each row carries the bars that decide the answer, the returns measured at three different horizons, what two naive rules predicted, and — the loudest one — every remaining bar of that trading day, to the closing bell. That data is kept because it is cheap to store now and impossible to add later: a regenerated bank would have to re-fetch every session, and there is no promise that a vendor's history window still reaches back that far.
Which means the answer is sitting in the same record as the question, and every step downstream is one careless field list away from shipping it.
That risk is not managed by remembering. The publisher names those fields explicitly as answer-bearing, and it refuses to run if any of them appear in the column list for the competitive database — the failure is an abort, not a warning. The two sets of fields that go out, one for the app bundle and one for the database, are written as allowlists rather than as things to strip, because a list of what may leave is safe when someone adds a new field and a list of what must be removed is not.
At the last step, the server that deals a competitive question selects the answer (it has to; it is the thing that judges you), writes it to the round's own storage, and removes it from what it sends back. The app is given the chart and never the answer, and your call is verified server-side.
There is an honest limit to that, and it is a design trade rather than an oversight. Classic mode is bundled into the app so a card deals instantly with no round trip, and a bundled question is judged on the device — so the practice bundle necessarily contains its own answers, and a determined person could read them. That is why the two pools are disjoint by construction: no question that ships in the bundle is ever dealt in a competitive mode.
4. The next question#
The fourth one is not inside any single question. It is between two of them.
Two questions taken from the same session an hour apart share most of their history — and the later one's chart contains the earlier one's answer bars, drawn in. A player who meets both sees the first question's outcome already on the screen the second time. Each question is individually clean; the pair is not.
Our generator takes at most one question per symbol per trading day, keeping the day's most decisive setup. It also spaces decision points far enough apart that the outcome windows never overlap, so no two answers in the bank are decided by the same bars.
That is a real cost, and it is paid in candidates rather than in the final selection. Fifty symbols over roughly 1,080 trading days could offer 54,000 sessions; capped at one question each, the generator found 53,444 candidates in total. Without the cap the same four years would have yielded several hundred thousand overlapping ones. Questions were the cheap thing to give up.
And a fifth, if you are measuring anything#
The same bias has a statistical form, which is where we met it first.
Before any of this was built we ran a probe to find out whether intraday price action carried anything readable at all. The trap there is that overlapping sample windows share bars, so they are not independent observations, and a significance figure computed over them is inflated — a confident-looking number produced by counting the same evidence repeatedly. The probe discards overlapping windows for that reason, and balances the sample 50/50 between up and down first, so that the baseline is a coin flip by construction and anything above it is real rather than a by-product of the market's upward drift.
What that measurement found is now a fixed property of the bank: one naive rule — fade the last candle — answers 60.6% of the selected pool correctly, and 20,767 of the 52,760 are the ones where it fails. Publishing aborts if the share of those in the competitive pool drifts more than five percentage points from the candidate pool it is drawn from, because a bank where the obvious read is always wrong is exactly as exploitable as one where it is always right.
What this does and does not buy#
All four layers together buy one thing: the question you are shown is a question that could actually have been asked at that moment, and nothing on the screen knows the answer.
They do not make the answer knowable. A chart with no look-ahead in it is still a chart of a market that did not have to do anything in particular next, and our whole filter band — moves between one and three times the symbol's own recent range — exists because below it the outcome is noise and above it the move is a shock nothing visible could have implied.
Which is the honest summary of what a clean question is worth. It cannot tell you that you were right. All it establishes, when you turn out to be wrong, is that the chart was not lying to you about what was knowable at the time. The rest of the parameters are on our methodology page.
Sources#
- SwipeTA question bank as published 2026-08-10: 20,300 competitive questions across 50 US equities and ETFs, balanced 10,136 up and 10,164 down, plus 1,500 fixed Classic questions across 22 symbols. Drawn from a generated pool of 53,444 candidates of which 52,760 were selected 26,380 up / 26,380 down; generator version 2026.07.20.
- https://www.swipeta.net/methodology