--> pinefixer: Understanding Repainting in Pine Script: Causes and Fixes
blog

Understanding Repainting in Pine Script: Causes and Fixes


Repainting is one of the most misunderstood concepts among Pine Script coders — and one of the most damaging if it slips into a strategy you're actually trading on. Here's what it actually means, the most common causes, and how to avoid it.

What Repainting Actually Means

A script "repaints" when a value it plotted or a signal it fired on a past bar changes retroactively as new data comes in. In practice, this means your backtest can look far better than how the script would have actually behaved live — because in the backtest, the script had information it wouldn't have had in real time.

Cause 1: Referencing the Current, Still-Forming Bar

If your logic uses values from the current unclosed bar (like high or low without an offset), those values keep changing until the bar closes. A signal that looked valid mid-bar can silently disappear once the bar finishes forming. This is the single most common repainting cause.

Cause 2: Misusing request.security() With Lookahead

When pulling data from a higher timeframe, using lookahead = barmerge.lookahead_on allows the script to see that higher-timeframe bar's data before it would actually be available in real time. This is the classic repainting trap for multi-timeframe indicators. The safer default is barmerge.lookahead_off, combined with referencing the previous, fully-closed bar using a [1] offset inside the security call.

request.security(syminfo.tickerid, "240", close[1], lookahead = barmerge.lookahead_off)

Cause 3: calc_on_every_tick in Strategies

By default, a strategy's calc_on_every_tick parameter affects how often the strategy recalculates during a live, forming bar. Leaving this on can make a strategy behave differently between backtesting and live/paper trading, since intrabar recalculation isn't identical to how the historical backtest engine processes closed bars.

How to Check If Your Script Repaints

A practical test: load the indicator on a chart, note where a signal appeared, then refresh the page or scroll away and back. If the signal has moved, shifted bars, or disappeared entirely without new data actually arriving, that's repainting in action. For higher-timeframe logic specifically, compare the indicator's behavior on a lower timeframe against loading the higher timeframe directly — a non-repainting script should agree with itself either way.

The General Fix

The safest pattern across almost all repainting scenarios is the same: only ever act on fully closed bar data. Use [1] offsets inside request.security() calls, avoid lookahead_on, and gate any once-per-higher-timeframe-bar logic behind a proper new-bar detection check rather than assuming the security call only updates once.

Building a multi-timeframe indicator?

If you're hitting compiler errors while implementing non-repainting logic, paste the exact message into the free Error Explainer for a plain-English fix.

Open the Error Explainer →