Why Your Pine Script Indicator Repaints (And How to Fix It)

axilon8 Avatar

Your Pine Script indicator repaints because it calculates or plots differently on historical bars than it does on the live, unconfirmed bar. TradingView’s own definition is exactly that: repainting is “script behavior causing historical vs realtime calculations or plots to behave differently.” The fix is always the same principle — make the script act on confirmed data only. In practice that means one of six patterns: gate signals with barstate.isconfirmed, reference the previous bar with [1], request higher-timeframe data with the lookahead_on + [1] pattern, stop plotting pivots into the past, avoid varip/timenow for backtested logic, and turn off calc_on_every_tick on strategies you intend to trust. As of July 2026, all of this applies to Pine Script v6, the current version.

I am Jayadev Rana, a TradingView-certified Pine Script developer. In my 8 years coding Pine Script — over 7,700 strategies shipped for clients in 14+ countries — “my indicator repaints” is easily the most common complaint that lands in my inbox. Nearly every time, the trader thinks it is one bug. It is almost never one bug: repainting has several distinct causes, and each needs its own fix. Here is the complete map.

First, know that not all repainting is bad

Before you rip your script apart, understand this: TradingView estimates that more than 95% of all indicators exhibit some form of repainting behavior. Common tools like RSI and MACD show a confirmed value on closed bars but fluctuate on the live bar until it closes — that is technically repainting, and it is completely acceptable. A volume profile that updates in realtime is not “broken.”

What matters is which kind of repainting you have. Widespread-but-acceptable (the live bar updating) is fine if you know about it. Potentially misleading (pivots plotted into the past, calc_on_every_tick strategies, unconfirmed HTF data) and unacceptable (leaking future data onto historical bars) are the ones that blow up live accounts. The rest of this guide is about killing those.

Cause 1: Fluid realtime values (the #1 cause)

Historical bars store only open, high, low and close. But on a live bar, the high, low and close are fluid — they change tick by tick until the bar closes. Only the open is fixed. So any signal built on the live close, high or low can flip on and off before the bar closes, then “repaint” into its final state.

This classic crossover indicator repaints for exactly that reason:

//@version=6
indicator("Repaints - live close crossover", overlay = true)
ma = ta.ema(close, 5)
xUp = ta.crossover(close, ma)
xDn = ta.crossunder(close, ma)
plot(ma, "MA", color.black, 2)
bgcolor(xUp ? color.new(color.lime, 80) : xDn ? color.new(color.fuchsia, 80) : na)

The green cross you see mid-bar can vanish before the bar closes. The fix is to only act on confirmed values. The cleanest way is barstate.isconfirmed, which is only true on the bar’s final tick — and historical bars are always confirmed, so behavior matches:

//@version=6
indicator("Non-repainting - confirmed cross", overlay = true)
ma = ta.ema(close, 5)
xUp = ta.crossover(close, ma) and barstate.isconfirmed
xDn = ta.crossunder(close, ma) and barstate.isconfirmed
plot(ma, "MA", color.black, 2)
bgcolor(xUp ? color.new(color.lime, 80) : xDn ? color.new(color.fuchsia, 80) : na)

You can also reference the previous, already-closed bar with the history operator [1] (e.g. ta.crossover(close, ma)[1]), or build the whole calculation on close[1]. All of these work — and all of them share one unavoidable trade-off: a non-repainting signal fires one bar later than a repainting one. You cannot have both instant signals and stability. Anyone promising you both is selling something.

Cause 2: request.security() without an offset

Pulling higher-timeframe (HTF) data is the second big source of repainting. request.security() behaves differently on historical versus realtime bars: on history it returns only confirmed HTF values, but in realtime it can return the still-developing HTF value, which keeps changing until the HTF bar closes. When the script reloads, those realtime bars become historical and the values change — a repaint.

The officially recommended non-repainting pattern is to offset the expression by one bar and turn lookahead on, so you always read the last confirmed HTF value:

//@version=6
indicator("HTF close - non-repainting", overlay = true)
htfClose = request.security(syminfo.tickerid, "D", close[1], lookahead = barmerge.lookahead_on)
plot(htfClose, "Confirmed daily close", color.aqua, 2)

The [1] offset and lookahead_on are interdependent — remove either and you break it. The far more dangerous mistake is using lookahead_on without the offset, which leaks future HTF prices onto historical bars and makes a backtest look like a money printer that dies instantly live. That mechanic is subtle enough that I gave it its own deep-dive: read request.security() lookahead in Pine Script for the full per-version behavior, the future-leak demo, and the multi-series pattern. This page is the overview; that one is the deep mechanics.

Debugging a multi-timeframe indicator that backtests beautifully and then falls apart live? Message me on WhatsApp at +91 77352 68199 and I’ll tell you within minutes whether it is a lookahead leak or a live-bar issue.

Cause 3: Pivots and offsets that plot into the past

Functions like ta.pivothigh(5, 5) can only confirm a pivot 5 bars after it happened. Many scripts then draw the pivot label back on the actual pivot bar. On history that looks perfect — but it fools traders into thinking the marker appears in realtime at the pivot, when really it only appears 5 bars later. That backward placement is repainting by relocation.

//@version=6
indicator("Pivot - plotting in the past", overlay = true)
// Off by default; user must opt in to backward plotting
plotInThePast = input(false, "Plot pivots in the past")
pHi = ta.pivothigh(5, 5)
if not na(pHi)
    label.new(bar_index[plotInThePast ? 5 : 0], na, str.tostring(pHi, format.mintick),
         yloc = yloc.abovebar, style = label.style_label_down, textcolor = color.black)

The honest default is offset 0 — place the marker where it is actually detected. If you want the prettier historical view, expose it as an input the user consciously switches on, so nobody is misled about when the signal is really available.

A related, sneakier trap: functions like ta.valuewhen(), ta.barssince() and ta.ema() depend on where the chart’s history starts. Because your first visible bar shifts over time (and with your TradingView plan’s bar limit), an early-history change can ripple forward and quietly alter recent values. This is “dataset variation” repainting — not a code bug you can fully patch, but something to be aware of when a script that looked stable yesterday reads slightly differently today.

Cause 4: varip and timenow

Variables declared with varip persist across realtime ticks — state that simply cannot be reproduced on historical bars, where only OHLC exists. A varip-based script may be genuinely useful for realtime alerts, but its logic cannot be backtested and its historical plots will not reflect what it does live. Same story for timenow, which returns the current wall-clock time: a script using it can never show consistent historical and realtime behavior, so it necessarily repaints.

The fix is a design decision, not a one-liner: if you need backtestable, historically-honest logic, keep varip and timenow out of the calculation path and build on standard var/series values plus barstate.isconfirmed.

Watch out for barstate.isnew too. It is true on a historical bar’s close but on a realtime bar’s open — a built-in historical/realtime mismatch. barstate.isconfirmed is the bar-state variable that avoids repainting; most others introduce it.

Cause 5: calc_on_every_tick on strategies

For strategies, calc_on_every_tick = true makes the strategy recalculate on every realtime tick, while on historical bars it only runs at the close. The two will almost never produce identical fills, so the strategy repaints and its backtest stops representing its live behavior. For any strategy whose backtest you actually want to trust, leave it at the default:

//@version=6
strategy("Confirmed-bar strategy", overlay = true, calc_on_every_tick = false)
htfEma = request.security(syminfo.tickerid, "D", ta.ema(close, 20)[1], lookahead = barmerge.lookahead_on)
long = ta.crossover(close, htfEma)
if long and barstate.isconfirmed
    strategy.entry("L", strategy.long)

Note how this combines two fixes: the confirmed-HTF pattern for a stable daily EMA, and barstate.isconfirmed to gate the actual order to a closed bar.

Cause 6: Unavoidable data revisions

Finally, some repainting is not your fault and cannot be coded away. Exchanges sometimes revise elapsed realtime bars with small price corrections that get written to the historical feed, and stock splits rewrite history entirely. When the chart refreshes, those revised numbers replace what your script saw live. There is no code fix — just know it exists so you don’t chase a phantom bug.

The repainting fix cheat sheet

Cause Symptom Fix
Fluid live values Signal flips before bar closes barstate.isconfirmed or [1] offset
request.security() no offset HTF line changes on reload close[1] + lookahead_on
Future leak (lookahead_on, no [1]) Backtest too good to be true Add the [1] offset — never ship without it
Pivots plotted in the past Markers appear late in realtime Offset 0 by default; input to opt in
varip / timenow / barstate.isnew Live plots differ from history Keep out of backtested logic
calc_on_every_tick = true Live fills differ from backtest Set to false for trustworthy tests

If you are on an older version, note that all of the fixes above are written for v6, the current release — see my breakdown of the latest Pine Script version and what changed if you still have //@version=5 at the top of your scripts. Repainting also frequently travels with other performance and logic issues; my write-up on 10 Pine Script bugs that slow down your strategy pairs well with this one. New to the language? Start with how to learn Pine Script quickly. And if you want a script fully audited for repainting before you risk capital, that is exactly what my Pine Script development service handles — including the broker-bridge layer covered on my MT4/MT5 automation page.

Frequently asked questions

How do I know if my Pine Script indicator is repainting?

Add your script to a live market and watch a forming bar: if a signal, arrow or background appears and then disappears before the bar closes, it repaints on the live bar. For deeper checks, reload the script on a chart with recent realtime bars — if historical values shift after the reload, you have HTF or dataset repainting.

Does barstate.isconfirmed stop all repainting?

It stops live-bar repainting for chart-timeframe logic, because it only evaluates true on a closed bar. But it does not confirm higher-timeframe data pulled through request.security() — for that you still need the lookahead_on + [1] offset pattern.

Why does my strategy backtest look great but lose money live?

The two usual culprits are a request.security() future leak (lookahead_on with no [1] offset) and calc_on_every_tick = true. Both let the backtest use information the strategy could never have had live. Fix the lookahead pattern and disable tick-by-tick calculation, then re-test.

Is repainting always bad?

No. Over 95% of indicators repaint in some form, and much of it — like RSI updating on the live bar — is harmless if you understand it. Only misleading repainting (future leaks, backward-plotted pivots, tick-based strategy fills) genuinely damages live decisions.

Can I fix repainting without delaying my signals?

Not entirely. Every non-repainting method acts on confirmed data, which by definition arrives one bar later than the live signal. The stability you gain is worth the one-bar delay; a signal you can trust beats a fast one that vanishes.

Still fighting a repaint you can’t pin down? Send me the script on WhatsApp at +91 77352 68199 and I’ll help you diagnose exactly which of the six causes it is.

Disclaimer: Trading and algorithmic strategies carry substantial risk of loss and are not suitable for every investor. Nothing in this article is financial advice or a promise of profit. Code examples are educational and should be tested thoroughly on your own data before trading real money. Details are accurate as of July 2026 per TradingView’s official Pine Script documentation and may change with future updates.

Leave a Reply

Your email address will not be published. Required fields are marked *