This one isn't from a client audit — it's a platform mechanic every Pine Script strategy that pulls in a higher timeframe should be checked against, because it's easy to introduce by accident and TradingView's own documentation explicitly warns about it. If your backtest and your live or paper results have quietly stopped agreeing and you've already ruled out fees and slippage, this is the next thing to check.
What request.security() actually does
request.security() pulls a value computed on a different timeframe (or symbol)
into your script's current context — the standard way to check, say, the 1-hour trend from a
15-minute chart. It takes a lookahead parameter that controls how it fills in values
on historical bars. As of the current Pine Script version, its documented default is
barmerge.lookahead_off — meaning out of the box, it does not leak information from
the future into the past.
The bug shows up when someone explicitly sets lookahead=barmerge.lookahead_on.
People usually do this on purpose, not by accident — it removes what looks like an annoying
one-bar lag on the higher-timeframe value, and plenty of forum answers recommend it for exactly
that reason. TradingView's own docs carry a direct warning about this setting:
"When using barmerge.lookahead_on without an offset, ensure that it does not
compromise the integrity of the script's logic by leaking future data into
historical chart bars."
The naive pattern
//@version=6
strategy("HTF trend filter")
htfClose = request.security(syminfo.tickerid, "60", close,
lookahead=barmerge.lookahead_on)
if close > htfClose
strategy.entry("Long", strategy.long)
On historical bars, this compares the current price against the closed 1-hour value — including, for intraday bars still inside that hour, a close that in a live sense hasn't happened yet. The backtest engine has the full historical record, so it fills in that value without complaint. Every entry signal in the Strategy Tester is quietly using information that wasn't actually available at the moment a live bot would have had to act on it.
This is one failure shape out of several I check for when a bot's live results stop matching its backtest. The full checklist — entry-price path and exit/risk-limit path together — is on one page, or I can walk through it against your script directly.
Get the safety-net checklist →Why it doesn't show up while you're watching it live
This is the part that makes the bug hard to catch by eyeballing the chart. TradingView's docs describe exactly this: on the currently-forming realtime bar, a lookahead series and a no-lookahead series plot the same value — there's no future data to leak yet, because the bar hasn't closed. The divergence only appears retroactively, once that realtime bar becomes historical and gets recalculated with the full record available. The docs call this repainting: the value you watched happen live and the value the same bar shows after a reload are two different numbers, and only the second one is what your backtest actually ran on.
So a live paper-trading session can look like it's confirming the strategy works — the signals you watch fire in real time match what the backtest predicted — right up until you reload the chart and the historical bars quietly repaint into a different story.
A second, easy-to-miss variant
The same function has a related but separate gotcha when the requested timeframe is at or below your chart's own resolution — for example, requesting 1-minute data on a 1-minute chart, or any case where the "higher" timeframe you're pulling isn't actually higher. TradingView's docs are specific about what you get back in that case:
"When calling this function to access a lower timeframe, it will evaluate
the expression from the LTF context. However, it returns the results from
only a single intrabar (LTF bar) on each chart bar."
Which intrabar you get depends on the same lookahead setting — the first intrabar
of the period with lookahead_on, the last with lookahead_off. It's a
different mechanism from the repainting issue above, but the practical effect is the same shape of
problem: the value your logic reacts to in a backtest isn't necessarily the value a live bot would
have seen at that moment, and the mismatch is invisible unless you go looking for it.
What to check in your own script
- Grep every
request.security()call in your script for thelookaheadargument. If it's not set, you're on the safe default. If it's set tobarmerge.lookahead_on, that call needs a second look. - For any call using
lookahead_on, check whether the requested series has an explicit historical offset (a trailing[1]) applied. Without one, you're very likely leaking data into your backtest that a live bot would not have had. - If you're requesting a timeframe at or below your chart's own resolution, confirm which intrabar you're actually getting back and whether that's the one your logic assumes.
- Reload the script after watching a live/realtime session and compare: did any of the values on the most recently closed bars change from what you watched happen? That's the direct, observable symptom of repainting.
None of this requires an exotic setup — a higher-timeframe trend filter is one of the most
common things people add to a strategy, and lookahead_on is a documented, legitimate
parameter with real uses. The failure isn't in wanting the feature; it's that the difference
between "correct on historical bars" and "correct at the moment a live bot has to decide" is easy
to miss unless you know specifically where to look. It's the same underlying lesson as the audit
bugs I've written about before: what a bot's numbers show and what was actually knowable at the
time are two separate claims, and a backtest alone won't tell you which one you're looking at.