Overview
Repainting is the most common and most costly mistake in Pine Script. A script repaints when its historical values differ from what it would have shown in real time on those same bars. The Strategy Tester accepts repainting data and generates profitable-looking results that do not hold up in live trading. This page covers the request.security mechanics that produce repainting, the barmerge flags, and the pattern that eliminates it.
Understand when repainting happens
A script repaints when it reads data that was not available at the time the bar was forming. The three common causes:
-
request.securitywithlookahead=barmerge.lookahead_onreads the final value of a higher-timeframe bar before that bar closes. On historical bars, the final value is already known, so the signal looks precise. In real time, the bar is still forming and the value changes until close. -
request.securitywithout a[1]offset on the requested series reads the current (forming) value of the higher-timeframe bar. On history, TradingView already knows the bar’s final value, so it looks stable. In real time, it shifts as the bar updates. -
Scripts that use
securityto get the current bar’s OHLC and trade on that same bar simulate a fill at a price that could not have been known when the entry decision was made.
Use the no-repaint pattern for higher-timeframe data
The safe pattern: request the previous completed bar of the higher timeframe, not the current forming one.
//@version=5
indicator("HTF Signal", overlay=true)
// WRONG: reads the forming HTF bar, repaints
htfCloseWrong = request.security(syminfo.tickerid, "240", close)
// CORRECT: reads the last completed HTF bar
htfClose = request.security(
syminfo.tickerid,
"240",
close[1],
lookahead = barmerge.lookahead_off
)
plot(htfCloseWrong, "Wrong", color=color.red)
plot(htfClose, "Correct", color=color.green)close[1] on the higher-timeframe series requests the value of the close from the bar that completed before the current HTF bar started. Combined with lookahead=barmerge.lookahead_off (the default), this mirrors what a live trader would have seen.
Know the difference between barmerge modes
barmerge.gaps_off (default): fills gaps between HTF bars with the last known value, so the series has a value on every lower-timeframe bar.
barmerge.gaps_on: returns na for lower-timeframe bars where a new HTF bar has not yet closed. Use this when you specifically want to act only on bar-close signals and need to distinguish “no new data” from “data unchanged.”
barmerge.lookahead_off (default): does not use future data. Always leave this as the default in production scripts.
barmerge.lookahead_on: uses the final value of the forming HTF bar. This makes historical plots look clean at the cost of lookahead. Valid only in a research context where the label clearly states “lookahead enabled.”
Detect repainting visually
The quickest test: add the signal to a chart, scroll back 3-6 months, and compare the indicator values to what you remember or what screenshots show from that period. If the current indicator shows a different value than it did in real time, it is repainting.
A more systematic test: publish the script, load it, then compare the chart after TradingView rerenders. If labels or line endpoints shift on zoom or after a page reload, repainting is confirmed.
Validate strategy signals on bar close only
In a strategy, set calc_on_every_tick = false (the default). This means fills compute at bar close, not on intrabar ticks.
//@version=5
strategy("Safe Strategy", overlay=true, calc_on_every_tick=false)
htfClose = request.security(syminfo.tickerid, "D", close[1], lookahead=barmerge.lookahead_off)
if close > htfClose
strategy.entry("long", strategy.long)Even with calc_on_every_tick=false, intrabar strategies can repaint if they use the current bar’s high or low to compute signals. Reference only close, close[1], or confirmed HTF values to be safe.
Avoid multiple request.security calls for the same symbol and timeframe
Each request.security call is a separate request to TradingView’s data feed. Multiple calls to the same symbol and timeframe are redundant and slow script execution.
// WRONG: two calls for the same series
htfO = request.security(syminfo.tickerid, "D", open[1])
htfC = request.security(syminfo.tickerid, "D", close[1])
// CORRECT: one call with a tuple expression
[htfO, htfC] = request.security(syminfo.tickerid, "D", [open[1], close[1]])TradingView limits the number of request.security calls per script. Consolidate calls to the same symbol and timeframe to stay well under the limit and avoid performance warnings. For library patterns that use security calls, see pine-script-libraries.