Skip to main content

Stop Repainting in Pine Script: Fix Signals With Confirmed Data

· 11 min read
Pineify Team
Pine Script and AI trading workflow research team

Repainting is the gap between what your Pine Script indicator shows in backtesting and what it actually signaled in real time. I've seen this destroy more trading strategies than bad entry logic ever could. A few weeks ago I was fine-tuning a mean reversion strategy on TSLA — the backtest showed a clean 72% win rate. When I paper-traded it live, the signals kept flickering on and off. That's repainting.

Understanding Repainting

What Exactly Is Repainting?

Think of it like watching a sports replay where the final score changes every time you rewatch it. Repainting means your indicator's historical signal positions shift as new bars form. The bar that looked like a textbook buy signal at yesterday's close might have been flashing sell signals all day while it was still forming.

The root cause is simple: your script references data that hadn't settled when that bar was open. It's like grading a test before the student finishes writing it. The answer might look right halfway through, then change completely by the end.

The Two Flavors of Repainting

Historical repainting alters your indicator's past values. An RSI level or moving average crossover that appeared clean at the close can ghost to a different spot once more bars appear. This makes backtests unreliable because the signals you're evaluating never existed when those bars were actually live.

Real-time repainting happens on the current, unfinished bar. As price ticks stream in, your indicator keeps reversing direction. I've watched a strategy flip between buy and sell four times in 30 seconds on SPY. That's not a trading edge — it's noise.

Why Pine Script Indicators Repaint

The main cause: using data from unconfirmed bars. When you reference close, high, or low with no offset, you're pulling the current bar's live values — numbers that change with every tick until the bar closes.

Another common source is request.security() pulling data from a higher timeframe that hasn't closed yet. If you're on a 5-minute chart reading daily data, and that daily bar is still forming, the daily values update in real time. I prefer to handle this case with explicit barstate checks inside the security call.

The Best Pine Script Generator

Sometimes repainting creeps in through look-ahead functions or improper handling of the switch between historical and real-time calculation modes. These edge cases are harder to catch but just as destructive.

Proven Solutions That Stop Repainting

1. Use Confirmed Bar Data Only

The simplest fix is to reference the previous closed bar instead of the current one. Why it works: a closed bar's data is final and will never change. What can go wrong: you introduce a one-bar delay. On a 1-minute chart this might cause you to miss fast entries, so test whether that lag matters for your timeframe.

//@version=5
indicator("No Repaint Example", overlay=true)

// Instead of this (repaints):
currentHigh = high

// Do this (doesn't repaint):
confirmedHigh = high[1]

plot(confirmedHigh, color=color.red, title="Previous High")

2. Master the barstate Variables

Pine Script gives you barstate.isconfirmed to gate your logic. Why it works: the block only executes on fully closed bars, so no signal can flip from live tick data. What can go wrong: using barstate alone won't fix repainting from higher timeframe requests. I've seen traders add barstate.isconfirmed and still get flickering signals because their request.security() source data remains unconfirmed.

if barstate.isconfirmed
// Only execute when the bar is finished
// This prevents repainting on real-time bars

if not barstate.isrealtime
// Only execute on historical bars
// Useful for backtesting without real-time noise

3. Fix request.security() Properly

When pulling higher timeframe data, you need an extra guard. Why it works: the ternary source[barstate.isconfirmed ? 0 : 1] picks the previous bar's data when the current higher-timeframe bar is still open. What can go wrong: this adds a one-bar lag to your HTF reference. On a 5-minute chart pulling daily data, you're using yesterday's close rather than today's. That's acceptable for most strategies but worth knowing.

//@version=5
indicator("Safe HTF Data", overlay=true)

// Safe way to get higher timeframe data
getSafeHTFData(symbol, timeframe, source) =>
request.security(symbol, timeframe, source[barstate.isconfirmed ? 0 : 1])

// Get daily high without repainting
dailyHigh = getSafeHTFData(syminfo.tickerid, "D", high)
plot(dailyHigh, color=color.orange)

4. Smart Alert Configuration

For alerts, restrict them to confirmed bars. Why it works: alert.freq_once_per_bar_close fires once when the bar closes, preventing a flood of notifications. What can go wrong: if your internet drops during the bar close, you won't get a retrigger until the next bar closes. Consider adding a backup notification channel if the signal is time-sensitive.

longCondition = ta.crossover(ta.sma(close, 10), ta.sma(close, 20))

if longCondition and barstate.isconfirmed
alert("Buy Signal Confirmed", alert.freq_once_per_bar_close)

Testing Your Indicators for Repainting

Run your indicator on a live chart, take a screenshot, then refresh the page or switch timeframes and come back. If any past signals moved or disappeared, you've got repainting.

I prefer a more thorough method. Run the indicator on a paper account for a full trading day, note every signal with a timestamp, then check the next day to see if those same signals still appear at the same positions on the historical chart. I tested this on a EURUSD 15-minute chart last month — the repainting caused a 0.8% difference in average win vs. the backtest.

For building more sophisticated trading strategies, you'll want to verify each component is repaint-free before stacking them together. A single repainting signal can cascade through multiple conditions and create phantom entries.

Advanced Techniques for Complex Strategies

When combining multiple indicators across timeframes, every source needs confirmation. Volume repaints too on the current bar, so I always use volume[1] for any volume-based analysis:

confirmedVolume = volume[1]
volumeMA = ta.sma(confirmedVolume, 20)

For multi-condition strategies, isolate each condition and test it for repainting separately before combining. I haven't tested every possible combination of repainting fixes with multi-timeframe setups, so I'd recommend verifying each data stream independently.

When using ATR-based stops, reference confirmed ATR values to avoid premature exits caused by repainting on the current bar.

Real-World Application Tips

Paper trade your repaint-free indicator for at least a few weeks before committing real money. I've found repainting issues are more obvious in choppy, sideways markets and subtler during strong trends, which makes them harder to catch in simple backtests.

Consider a confirmation filter: require the signal to hold for two consecutive confirmed bars before acting. This adds latency but cuts false signals significantly. You'll miss the first tick of a move but catch the meat of it.

Tools That Handle Repainting Automatically

Building repaint-free indicators from scratch is time-consuming, especially with multi-timeframe logic. Tools like Pineify can generate indicators with repainting prevention baked in, so you can focus on strategy logic instead of plumbing.

Pineify Pine Script Editor

But I'll be honest — no automated tool catches every edge case. You still need to verify the output and understand the underlying logic. Check out what Pineify can do if you want to skip the manual setup, but plan to run your own repainting checks afterward.

Pineify Pine Script Generator

The advantage of such tools is that they handle the standard repainting prevention patterns automatically. The tradeoff is that you trade some control for convenience.

What is repainting in Pine Script?

Repainting means your indicator changes its historical signals after the bar closes. A signal that looked like a buy at the close could look different the next day because the indicator used data that wasn't confirmed when that bar was still forming.

How do I use barstate.isconfirmed to prevent repainting?

Wrap your entry logic in an if barstate.isconfirmed block so it only runs on completed bars. This guarantees the signal won't flip as new ticks arrive, because no more data is coming for that bar.

Why does request.security() cause repainting?

request.security() reads data from another timeframe. If that timeframe's current bar hasn't closed, the value keeps updating. The fix is to use source[barstate.isconfirmed ? 0 : 1] inside the call, which pulls the previous confirmed bar's data when the current bar is still open.

What is the difference between historical repainting and real-time repainting?

Historical repainting shifts past signals as new bars form, corrupting your backtests. Real-time repainting changes signals on the current open bar tick by tick, causing false alerts. Both are caused by reading unconfirmed bar data.

How can I test whether my Pine Script indicator repaints?

Run the indicator live, screenshot it, then come back the next day and compare. If past signals moved or vanished, it repaints. I also run two instances side by side — one with current bar data and one with confirmed data only — and watch the difference in real time.

Does using close[1] instead of close eliminate repainting?

Yes, close[1] is the previous bar's confirmed close and will not change. Using close without an offset reads the current bar's price, which updates with every tick. Shifting by 1 is the easiest repainting fix.