What repainting actually means
Repainting is when a script's output on a bar differs depending on when it was calculated. The same bar, the same script, two different answers — one while the bar was forming, another after it closed and the script recalculated from history.
It follows from something structural rather than from a mistake. A historical
bar holds four numbers: open, high, low, close. A realtime bar holds a close
that is still moving, and a high and low that can still extend [1]. A script
reading high on a forming bar is reading a value that is not final. When the
script later reruns over that bar as history, it reads the final one.
Not all repainting is a defect
This is the part that gets lost, and getting it wrong in either direction is expensive.
Repainting that is working as intended: an indicator that updates as the current bar develops. You want that. A signal that only appears after the close is a signal you cannot act on at the close.
Repainting that invalidates a backtest: a script whose historical values could not have been known at the time they are plotted. This is the one that makes an equity curve look extraordinary and mean nothing, because the historical run had information the live run will never have.
The test is not "does it change" — it is "could this value have been known on that bar, at that moment, in real time?" If no, the backtest is measuring a strategy that cannot exist.
TradingView is direct about the worst version: scripts that leak future data into history are "extremely misleading", and publishing them is not allowed [2].
The ten documented causes
TradingView documents ten distinct causes [1]. They are not equally serious, so they are grouped here by what they do to a backtest rather than by language feature.
Fatal — the backtest is measuring something impossible
| Cause | What happens |
|---|---|
Future leak via request.security() | Using lookahead without offsetting reveals prices from beyond the current bar, on historical bars only |
| Plotting in the past | Drawing onto earlier bars retroactively, so the chart shows a detection that did not happen at that time |
Both produce a historical record that could not have existed live. Nothing else on this list is in the same category.
Structural — expect a difference, and account for it
| Cause | What happens |
|---|---|
| Fluid data values | Historical bars hold only OHLC; a realtime bar's high, low and close are still moving |
Repainting request.security() calls | Returns an unconfirmed higher-timeframe value on realtime bars, which differs once the script restarts |
Lower-timeframe request.security() | Intrabars are not sorted in realtime, so realtime behaviour cannot be reproduced from history |
calc_on_every_tick strategies | Execute on every tick in realtime but only at bar close historically |
These four are the ordinary asymmetry between a bar that is finished and a bar that is not. They do not make a strategy fake; they make a backtest an approximation, and the size of the gap is worth knowing before trusting a result.
Design-dependent — correct until you assume otherwise
| Cause | What happens |
|---|---|
varip declarations | Keep state across realtime updates in a way history cannot reproduce |
| Bar-state built-ins | barstate.isnew and its siblings behave differently in realtime |
timenow | Necessarily differs between a historical run and a live one |
| Dataset variations | Where the chart's history begins, and later data revisions, change calculations over time |
The last one catches people who are sure their script changed when nothing in it did. A different chart start point, or a revision to the feed, is enough to move a result [1].
The question to ask of each
For every one of these, the useful test is the same: would a live run at that moment have had this value? The two in the fatal group answer no. The rest answer yes, with a timing difference you can measure.
Lookahead and the future leak
request.security() takes a lookahead parameter with two possible
values [2]:
| On historical bars | On realtime bars | |
|---|---|---|
barmerge.lookahead_off (default) | the last confirmed higher-timeframe value | the symbol's current unconfirmed value |
barmerge.lookahead_on | values from times beyond the bar it executes on | the same unconfirmed value as lookahead_off |
Read the second row twice. On history, lookahead_on hands the script data
from the future. In realtime it cannot, because that future does not exist yet
— so it returns exactly what lookahead_off would.
That asymmetry is the whole problem. The historical half of the run is answering with information the live half will never have, and both halves are the same script. The backtest is not optimistic; it is describing a different strategy.
TradingView's own wording: programmers should exercise "extreme caution" with lookahead, especially on higher timeframes [2].
Using it correctly
Lookahead is not forbidden, and there is a documented pattern that makes it safe. Two things together [2]: offset the requested expression with the history-referencing operator, and enable lookahead.
float htfPrice = request.security(syminfo.tickerid, timeframe,
high[1], lookahead = barmerge.lookahead_on)
The [1] is what makes this sound. Asking for the previous higher-timeframe
bar's high, with lookahead on, gives a value that was genuinely available at
that point — so the series behaves identically on historical and realtime bars,
and does not repaint [2].
Without the offset, the same call with lookahead_on is the future leak.
If you did not write the script
Two checks, in order, before trusting any strategy you did not write:
- Search it for
lookahead. Ifbarmerge.lookahead_onappears, find the expression being requested and confirm it is offset —high[1], nothigh. - Check whether it uses
request.security()at all. If it does and the backtest looks remarkable, this is the first thing to rule out.
Publishing a script that leaks future data into history is not permitted on TradingView [2], which tells you how common the mistake is.
Tactix AI on this workflow
AlfaTactix includes Tactix AI: use Tactix Studio to describe your idea in plain language and draft timeframes, signals, filters, and risk into the same six-step Strategy Builder form — or open Tactix Guide on any step when you only need a field explained. Review every value, then export MQL5 or Pine Script from Code Generator (form-first — not untested prompt-to-code).

