You build a multi-timeframe indicator. The backtest looks exceptional. You go live and the signals are completely different from what history showed. If you are using request.security and the signals do not match, the first place to look is lookahead. One parameter, two settings, and the wrong one introduces future data into every historical bar your indicator has ever plotted.
This post explains exactly what barmerge.lookahead_on does, why it is almost never the right choice, and what the correct pattern looks like. It also covers a second, subtler version of the same problem that persists even when lookahead is set to off.
What lookahead actually controls
When Pine Script evaluates request.security on a historical lower-timeframe bar, there may be several lower-timeframe bars within the same higher-timeframe bar. The lookahead parameter controls which value is returned for those intra-HTF bars.
With barmerge.lookahead_off (the default), each lower-timeframe bar within the daily bar gets the value as it stood at the start of that daily bar — the value from the close of the previous daily bar, carried forward. The daily bar's final value is only available to the lower-timeframe bar that corresponds to the daily close.
With barmerge.lookahead_on, each lower-timeframe bar within the daily bar gets the value from the end of that daily bar — the final settled close — even though the daily bar has not yet finished. On historical data, this information exists and is served. On live data, it does not exist yet and cannot be served. The historical backtest used future data. The live chart cannot replicate it.
//@version=6
indicator("Lookahead ON — future leak", overlay=true)
// NEVER DO THIS.
// lookahead_on tells Pine to use the value of the higher-timeframe bar
// BEFORE it has closed. On historical bars, this bar is already known,
// so the indicator reads the final settled value as if it was available
// at the start of the period. That is future data. The backtest looks
// perfect; live trading will not match.
htfClose = request.security(syminfo.tickerid, "D", close, lookahead=barmerge.lookahead_on)
plot(htfClose, "Daily close (future leak)", color=color.red)The subtler problem: lookahead_off is not enough on its own
Setting lookahead=barmerge.lookahead_off removes the future leak, but it does not solve the live-bar problem. Without a [1] offset inside the expression, the function still returns the value of the current, unfinished daily bar. On historical bars that daily bar is closed and the value is stable. On the live chart, the current daily bar is updating on every tick. The indicator still repaints.
//@version=6
indicator("Lookahead OFF but still wrong", overlay=true)
// lookahead_off (the default) alone is not enough.
// Without [1], this still reads the CURRENT open daily bar, not the last
// closed one. The value changes on every tick during the live daily session.
// Historical bars look stable because they are closed — but live will differ.
htfClose = request.security(syminfo.tickerid, "D", close, lookahead=barmerge.lookahead_off)
plot(htfClose, "Daily close (still repaints live)", color=color.orange)This is the version that catches the most developers: they read that lookahead_off is the safe setting, set it, and assume the problem is solved. It is not. The only complete solution is both lookahead_off and a [1] offset inside the expression.
The correct pattern
//@version=6
indicator("Correct MTF — confirmed bar", overlay=true)
// The ONLY safe pattern: [1] inside the expression + lookahead_off.
// [1] shifts to the previous bar of the daily series before fetching,
// so we always read the last fully CLOSED daily bar.
// The value is stable the moment that daily bar closes and never changes.
htfClose = request.security(syminfo.tickerid, "D", close[1], lookahead=barmerge.lookahead_off)
plot(htfClose, "Confirmed daily close", color=color.aqua)close[1] inside the expression shifts the series by one barwithin the higher timeframe before the value is fetched. The result is always the last fully closed daily bar's value. It is the same on bar 3 of a live trading day as it was at the start of the day. Historical and live behavior are identical.
A common mistake: offsetting outside the function
Older Pine Script tutorials sometimes showed a pattern that looks similar but does something different: fetching without an offset and then applying [1] to the returned series outside the function call. This does not offset within the higher timeframe. It shifts the entire returned series one bar to the right on the current timeframe, which misaligns the plot without fixing the repainting.
//@version=6
indicator("Legacy pattern — also wrong", overlay=false)
// Older Pine Script tutorials often showed this pattern:
// passing [1] OUTSIDE the function call.
// This does NOT achieve the same thing. It takes the confirmed value
// and then offsets the resulting series by one bar on the CURRENT
// timeframe, shifting the plot one bar to the right of where it belongs.
htfClose = request.security(syminfo.tickerid, "D", close, lookahead=barmerge.lookahead_off)
htfCloseOffset = htfClose[1] // WRONG — this offsets the output, not the input
// The correct version offsets INSIDE the expression:
htfCloseCorrect = request.security(syminfo.tickerid, "D", close[1], lookahead=barmerge.lookahead_off)
plot(htfCloseOffset, "Wrong offset", color=color.red)
plot(htfCloseCorrect, "Correct offset", color=color.aqua)When lookahead_on is ever valid
There is one legitimate use case: building a strategy that deliberately models perfect execution at the open of each higher-timeframe bar. Some academic backtesting methodologies assume you can act at the open of the next period on any signal from the close of the previous one. In that specific context, lookahead_on combined with careful interpretation of results is intentional rather than a mistake. It should still be accompanied by a comment explaining the choice explicitly, because anyone reading the code will assume it is a bug unless told otherwise.
For every other use case — any indicator or strategy intended to behave consistently between backtest and live trading — use the confirmed-bar pattern. The Pine Script repainting checker flags both lookahead_on and bare request.security calls without a [1] offset. The broader context of how request.security works — including tuple fetching and cross-symbol data — is in the request.security deep dive.
Disclaimer: PineScripter is a coding tool for Pine Script development. It does not provide financial advice and does not guarantee trading profits. Always backtest strategies thoroughly and understand the risks before live trading.