Pine Script repainting: what it is and how to detect it
Repainting is when a script's past signals change after the fact. The most common cause is intrabar evaluation — logic that reads a bar before it closes — and the standard fix is gating entries on barstate.isconfirmed, which makes the script act only on closed bars. Two other causes, higher-timeframe lookahead and back-adjusted series, need separate fixes.
A repainting indicator shows one thing while a bar is forming and a different thing once history is fixed. Your backtest is judged on the final, repainted version — which you could never have traded.
Cause 1: acting on the unclosed bar
Logic that fires intrabar can flip as the candle moves. At the close it looks like a clean signal; live, it flickered on and off. The fix is evaluating the signal only on a closed bar — see the barstate.isconfirmed section below for the exact pattern and its limits.
Cause 2: lookahead in higher-timeframe data
Requesting a higher timeframe with lookahead on hands past bars the final HTF value before it was known. Request it safely:
htfClose = request.security(syminfo.tickerid, "60", close[1],
lookahead=barmerge.lookahead_off)
Cause 3: future-referencing / dynamic series
Some functions and security calls back-adjust historical values (e.g. unconfirmed HTF, certain drawing or label updates). If a past value can change when new data arrives, it repaints. This is one of several Pine mistakes that only surface in live execution.
barstate.isconfirmed: what it does and why it stops intrabar repainting
barstate.isconfirmed is a built-in variable that is true exactly once per bar, on the final update of that bar — the moment the bar closes and its values stop changing. Everything evaluated inside that gate sees only finished candles, so a condition that was true at the close stays true in history. That is the definition of not repainting intrabar. TradingView documents the variable in the Pine Script v5 reference, though the reference states what it is rather than when to reach for it.
The pattern:
//@version=5
strategy("Confirmed-bar entries only", overlay = true)
longSignal = ta.crossover(ta.ema(close, 9), ta.ema(close, 21))
// barstate.isconfirmed is true only on the bar's closing update,
// so this block runs once per bar, never on a forming candle
if longSignal and barstate.isconfirmed
strategy.entry("L", strategy.long)
The cost is honesty rather than speed: the entry is available one bar later than the crossover appears visually, because the crossover only becomes a fact at the close. A correct backtest carries the same delay — fills at the next bar's open, never at the price the forming candle happened to show.
When it is not enough:
- It fixes only cause 1. Higher-timeframe lookahead and back-adjusted series repaint on fully confirmed bars too, and each needs its own fix from the sections above.
- Combining it with calc_on_every_tick=true in a strategy reintroduces intrabar evaluation elsewhere in the script. If the goal is confirmed-bar logic, leave tick calculation off.
- It does not change how alerts fire. An alert() call inside the gate fires at bar close, which is correct — but an alertcondition set to "Once Per Bar" in the alert dialog can still trigger intrabar regardless of what the code intends. Match the dialog setting ("Once Per Bar Close") to the code — the difference between strategy and indicator alerts matters here too.
- On realtime bars before the close, the gated block simply does not run. If a dashboard or label needs to update live, keep display logic outside the gate and only the order logic inside it.
How to detect it
- Bar Replay. Step bar-by-bar and watch whether past signals move. If they do, it repaints.
- Compare alerts to history. Log live alerts, then check if the historical chart later shows different signals at those times.
- Reload test. Note signals, refresh the chart; if they shift, you have repainting.
Why it matters for prop trading
A repainting system passes every backtest and fails every evaluation. On a trailing-drawdown account that means real blow-ups from signals that never truly existed. See also why backtests don't match live.
Puravida Edge signals evaluate only on confirmed bars and use lookahead-off data, so what you see in history is what fires live.
FAQ
What does repainting mean in Pine Script?
An indicator or strategy repaints when its past signals change after the fact — because it read an unclosed bar, used lookahead higher-timeframe data, or referenced values that get back-adjusted. The backtest then reflects signals you could never have traded.
What does barstate.isconfirmed do in Pine Script?
It is true only on a bar's final update, at the close. Gating logic on it means the script evaluates finished candles exclusively, which removes intrabar repainting. It does not protect against higher-timeframe lookahead or back-adjusted series, and the entry arrives one bar after the visual signal.
How do I check if my indicator repaints?
Use Bar Replay to step through candles and watch whether historical signals move, compare logged live alerts against the later historical chart, or reload the chart and see if signals shift.
How do I stop a strategy from repainting?
Gate logic on barstate.isconfirmed, request higher-timeframe data with lookahead off and a one-bar offset, and avoid functions that back-adjust historical values.
Not financial advice. Performance figures referenced are hypothetical, modeled outputs (1,500-path Monte Carlo on a backtest + live sample). Past performance does not guarantee future results. Tool names are referenced for education; verify current features and prop-firm rules directly.