Steven Imrich

HomeTrading platforms → How do I tell if a TradingView indicator repaints?

How do I tell if a TradingView indicator repaints?

Updated August 27, 2026

Short answer

Take a screenshot of the signals on a live chart, then reload the page and compare, and separately step through the same range in Bar Replay. If a signal moves, vanishes or appears on a bar it wasn't on before, the script is rewriting history. If it only flickers while the current bar is still forming and then settles at the close, that's not repainting in the sense people mean, that's every indicator ever written. TradingView's own docs put it bluntly: over 95% of indicators repaint in some form, including MACD and RSI.

The word is doing two different jobs

When someone says an indicator repaints they usually mean one of two completely separate things, and the argument in the comments section is normally between two people who each mean the other one.

The first meaning is that the script changes its mind about the bar you’re currently looking at, while that bar is still open. Price ticks up, the arrow appears. Price ticks back down, the arrow goes away. Every calculation on live data does this. RSI is a different number at 10:31:04 than it was at 10:31:03, and nobody calls RSI broken.

The second meaning is that the script goes back and changes bars that already closed, so the chart you look at today shows signals that were never on the screen at the time. That one matters. That’s the one that turns a mediocre tool into a hindsight machine.

TradingView’s own docs are blunt about the first kind. They estimate over 95% of indicators exhibit some form of repainting, MACD and RSI included, and the support article says outright it’s “not a bug”, just the difference between a historical bar (four numbers) and a live bar (a stream).

So “does it repaint” is the wrong question. “Does the signal on bar N still exist tomorrow, and was it there when bar N closed” is the right one.

Read the source first, if you can

Open the script and click the source tab. If it’s protected or invite-only you skip to the tests below, and that limitation is worth a thought before you pay for anything.

The pattern that causes real future leak is a higher timeframe request without an offset:

// leaks: on historical bars this hands you the completed daily close
// while the intraday bars of that same day are still running
d = request.security(syminfo.tickerid, "D", close, lookahead = barmerge.lookahead_on)

The docs are explicit that barmerge.lookahead_on without a [1] offset “will return data from the future on historical bars”, and that publishing that is against the rules. The safe form they give is:

d = request.security(syminfo.tickerid, "D", close[1], lookahead = barmerge.lookahead_on)

Same function, same lookahead, one offset, completely different honesty. If you see lookahead_on without a [1] on the expression, stop reading and assume the worst.

Other things worth grepping for:

  • barstate.isrealtime used to branch the logic. A script that computes one thing live and another thing on history is by definition two different scripts.
  • varip. These variables persist across ticks inside a bar and cannot be reproduced on history at all, so anything downstream of them is untestable.
  • timenow. Same problem, the value doesn’t exist historically.
  • request.security() pointed at a timeframe lower than your chart. Realtime intrabars aren’t sorted yet, so it behaves differently live. request.security_lower_tf() is the intended tool.
  • calc_on_every_tick = true in a strategy() declaration, which invalidates the backtest. More on that in the strategy tester page.

And one that is honest but still catches people out, pivots:

ph = ta.pivothigh(high, 5, 5)
plot(ph, offset = -5)   // drawn on the pivot bar, but not known until 5 bars later

Nothing dishonest is happening there, the high is real. But you find out five bars after the fact, and the plot puts the marker back where it belongs, so the chart makes it look like you knew at the time. The docs call this “plotting in the past”.

The reload test

Fastest and most conclusive of the lot, and it needs no code access.

Watch a live session and screenshot the last twenty bars with the indicator on them. Better still, hover the bars you care about and note the plot values in the Data Window, because numbers are easier to compare than pixels.

Then refresh the browser. Not switch tabs, actually reload, which forces a recalculation from scratch. Anything that moved between those two states moved because the script was carrying live state that doesn’t survive a rebuild. Do it again the next morning on the same range. If Tuesday’s arrows aren’t where they were on Tuesday, you’re done.

One wrinkle. The number of bars your plan loads changes the starting point of every recursive calculation, so EMAs and ta.barssince and ta.valuewhen all ripple from wherever the data begins. Plans get 5,000 bars at the low end, 10,000 on Essential and Plus, 20,000 on Premium, 25,000 on Expert, 40,000 on Ultimate. Tiny differences in the deep past are boring. Markers appearing or disappearing in the last week are not.

Bar replay, and what it won’t catch

Bar Replay is the button at the top of the chart. Pick a bar a few weeks back, step forward one bar at a time, and watch what the indicator does as each bar closes.

This catches lookahead beautifully. Replay chops the future off, so a script that was peeking simply stops drawing the signals it used to. It also catches plot-in-the-past pivots, because you watch the marker pop into existence five bars after the bar it lands on.

What it does not catch is live-bar behaviour, and that’s where people get overconfident. Replay feeds the script completed bars. It’s a history simulation, not a tick simulation, so a script can pass it perfectly and still flash signals all day that dissolve before the close.

The forming-bar test nobody bothers with

Sit on a 5 minute chart during a busy hour and just watch the current candle with the indicator running. Not the closed ones. The one that’s alive.

Count how many times the signal appears and withdraws inside a single bar. Three or four times per bar and you can’t trade the arrow, however clean the historical chart looks, and your alerts will contradict each other all session.

The fix isn’t a different indicator, it’s the alert frequency. Pine has alert.freq_once_per_bar_close, and the docs say alerts only trigger in the realtime bar, so waiting for the close is the only way to get an alert that matches the closed-bar chart. In script code the equivalent is guarding the condition:

buy = ta.crossover(close, ta.ema(close, 20)) and barstate.isconfirmed

barstate.isconfirmed is the one bar state variable that reduces repainting rather than causing it.

So when is it fine

An indicator that recalculates on the live bar and then locks at the close is fine, and calling it broken is just wrong. A volume profile filling in as the session goes is fine. A pivot marker that shows up late is fine, as long as you know it shows up late and you don’t backtest as though it didn’t. A script that draws a different past after a reload is not fine, and no setting rescues it.

Checking someone else’s closed-source product, the reload test and the overnight comparison are the only two you can actually run. Do both on the same range and write the values down. Memory is very generous about what the chart looked like yesterday.

Related questions