Pine Script Repainting: Causes, Tests, and Practical Fixes

Reviewed 13 August 2026

Repainting means a Pine Script’s historical display or calculations differ from what occurred while bars were updating in realtime. Some repainting is normal: the open bar’s close, RSI, and moving averages change as new ticks arrive. The dangerous cases are undisclosed behavior, future data leaking into history, alerts firing on temporary conditions, or markers being moved into the past.

The useful question is not simply “does it repaint?” Ask when values can change, whether alerts wait for confirmation, whether higher-timeframe data is confirmed, and whether the same behavior can be reproduced after the chart reloads.

Start with a reproducible test

Record:

  • symbol and exchange;
  • chart type and timeframe;
  • session and timezone;
  • script version and exact inputs;
  • alert condition and frequency;
  • whether the script is an indicator or strategy;
  • whether calc_on_every_tick, varip, pivots, or request.*() calls are used;
  • the bar time where the live and reloaded results differ.

Take a screenshot or log while the bar is open, again after it closes, and again after reloading the chart. This separates normal intrabar movement from historical revision.

Cause 1: using the open bar as if it were final

On a realtime bar, close, high, low, and indicators derived from them can change on every update. Historical bars contain only their final values. A crossover can therefore appear during the bar and disappear before the close.

If the intended signal is a close-confirmed signal, require confirmation:

//@version=6
indicator("Confirmed crossover", overlay = true)

fast = ta.ema(close, 10)
slow = ta.ema(close, 30)

bool longSignal = ta.crossover(fast, slow) and barstate.isconfirmed

plot(fast, "Fast EMA", color.teal)
plot(slow, "Slow EMA", color.orange)
plotshape(longSignal, title = "Confirmed signal", style = shape.triangleup, location = location.belowbar, color = color.lime)

TradingView documents barstate.isconfirmed as true on historical bars and on the closing update of a realtime bar. It can delay a signal, which is the intended trade-off. It does not fix every type of repainting and does not work as a confirmation mechanism inside a request.security() expression.

Cause 2: unconfirmed higher-timeframe data

A five-minute chart requesting the current daily close receives a value that develops throughout the day. After reload, historical bars retain only confirmed daily values, so the earlier intraday values are no longer available.

When the strategy requires the last completed higher-timeframe value, TradingView documents this pattern:

//@version=6
indicator("Confirmed daily close", overlay = true)

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

plot(dailyClose, "Previous confirmed daily close", color.orange)

The one-bar offset and barmerge.lookahead_on work together. Using lookahead on without the appropriate historical offset can leak a future higher-timeframe value into past chart bars. This confirmed pattern also adds delay because it waits for the higher-timeframe bar to finish.

Validate that the requested timeframe is actually higher than the chart timeframe. A reusable script can raise a runtime error when that assumption is violated.

Cause 3: lower-timeframe requests that return only one intrabar

request.security() is intended primarily for equal or higher timeframes. When used for a lower timeframe, it returns one intrabar value for each chart bar rather than all intrabars.

If the calculation needs every available lower-timeframe value, use request.security_lower_tf(), which returns an array. Handle empty arrays and differences in available history explicitly. Realtime and historical intrabar coverage may still differ.

Cause 4: alert frequency does not match the signal definition

An alert configured during an open bar can fire even when the final bar no longer satisfies the condition. For close-confirmed logic, create the alert from the updated script and select Once Per Bar Close, or use alert.freq_once_per_bar_close with alert().

After changing a script or its inputs, recreate the TradingView alert. Existing alerts run from a saved snapshot of the script, inputs, symbol, and timeframe; editing the chart copy does not automatically update that running alert.

Cause 5: strategies calculate differently in realtime

Strategies normally calculate at bar close. Enabling calc_on_every_tick makes a strategy recalculate on realtime ticks, but historical bars do not contain the same tick sequence. Results can therefore change after reload.

Likewise, process_orders_on_close changes simulated order timing. It may be useful for a specific model, but a same-close fill in the broker emulator does not represent webhook, network, broker, exchange, spread, and liquidity delays.

Disclose these settings and compare signal time, order creation time, and simulated fill time separately.

Cause 6: plotting a discovery into the past

Pivot functions need future bars to confirm a pivot. Plotting the confirmed pivot back on the pivot bar can be visually useful, but the marker was not available at that historical moment.

Label it as retrospective confirmation. Do not use the back-plotted marker as evidence that a realtime alert could have fired on the earlier bar.

Cause 7: varip and intrabar state

varip can persist state across updates inside a realtime bar. Historical bars usually do not contain the same sequence of updates, so intrabar state built with varip may not be reproducible in a historical backtest.

Use it only when realtime behavior is the purpose, document the limitation, and test alerts during a live or replay session. Do not claim historical parity that the dataset cannot provide.

A repainting audit checklist

  1. Compare the open-bar signal with the same bar after close.
  2. Reload the chart and compare the saved evidence.
  3. Inspect every request.security() call for timeframe, offset, and lookahead.
  4. Use request.security_lower_tf() when all intrabars are required.
  5. Check alert frequency and recreate alerts after changes.
  6. Review calc_on_every_tick, process_orders_on_close, and calc_on_order_fills.
  7. Identify pivots, negative plot offsets, or drawings moved into history.
  8. Identify varip, timenow, and realtime-only logic.
  9. Test standard candles separately from Heikin Ashi, Renko, and other synthetic charts.
  10. Document acceptable realtime movement and unacceptable future leakage separately.

What a non-repainting claim should disclose

A useful statement specifies:

  • whether signals wait for chart-bar close;
  • how higher-timeframe values are confirmed;
  • whether markers are plotted into the past;
  • whether alerts can trigger intrabar;
  • whether the strategy calculates on every tick;
  • which chart types and timeframes were tested;
  • which behavior remains different between realtime and history.

No short code pattern makes every script non-repainting. Confirmation usually adds delay, and some realtime information cannot be reconstructed from historical OHLC bars.

If you request a code review, provide the source, the exact chart setup, alert configuration, and one reproducible mismatch. Do not send broker credentials or access tokens.

Code examples are educational and are not trading advice. A stable historical plot does not guarantee live execution or profitability.

Primary sources

Leave a Reply

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