Pine Script Repainting: Why It Happens and How to Prevent It

Avoid misleading backtests caused by repainting in TradingView Pine Script. Learn what repainting is, why it happens, and how to prevent it using best practices like freezing stop-loss levels, confirming bars, and careful use of request.security().

pine-script-repainting-explained

If you’ve ever created a strategy in TradingView using Pine Script and thought, “Wow, this performs great in backtests!” — only to realize the results were too good to be true; you’ve likely been affected by repainting.

Repainting is a widespread issue that can mislead traders into thinking a strategy is far better than it really is. It happens when your script uses future information to influence past trades, something that’s simply impossible in real-world trading. Understanding and managing repainting is essential if you want to build strategies that genuinely reflect live market conditions.


What Is Repainting?

TradingView defines repainting as:

script behavior causing historical vs. realtime calculations or plots to behave differently.

In simple terms:

  • On historical bars, Pine Script shows finalized values.
  • On a realtime (unconfirmed) bar, values like close, ATR, or indicators such as MACD and RSI are still changing until the bar closes.

If your strategy recalculates stop-losses, take-profits, or signals based on live data every time a bar updates, you end up with signals that shift retroactively.

This is repainting and it destroys the credibility of your backtests.


Why Does Repainting Happen?

In Pine Script, all values are recalculated continuously as new prices come in during an open bar. If you use dynamic variables like close, atr, or rsi directly, and don’t freeze them, your strategy’s logic revises itself every second, creating misleading results.

Common Example of Repainting:

strategy.exit("TP/SL", from_entry="Long", stop=close - atr * 1.5, limit=close + atr * 2.0)

This looks logical. But because close and atr are live values, your exit levels move around as the bar forms, making the trade look "perfect" after the fact.

Impact: Your strategy appears far more profitable than it would be live.


Not All Repainting Is Bad

It’s important to understand:
Not all forms of repainting are wrong.

TradingView states that over 95% of indicators (even standard ones like RSI, MACD) technically repaint because live values aren't finalized until the bar closes.

Forms of repainting can be divided into:

Type Description Acceptable?
Widespread but acceptable Scripts using live close, ATR, or higher timeframe values ✔️
Potentially misleading Scripts moving past signals or shifting historical plots ⚠️
Unacceptable Scripts leaking future information into past bars

The goal is to prevent misleading or unacceptable forms of repainting in your strategies.

How to Prevent Harmful Repainting

Here’s how to build reliable, non-repainting scripts:


1. Lock Stop Loss and Take Profit at Entry

Instead of recalculating on every bar, store your stop/target levels once at the moment of entry.

✅ Correct Way:

var float sl = na
var float tp = na

if (entryCondition)
    sl := close - atr * 1.5
    tp := close + atr * 2.0
    strategy.entry("Long", strategy.long)
    strategy.exit("Exit", from_entry="Long", stop=sl, limit=tp)

This locks your exits to the original conditions; no "drifting" later.


2. Use barstate.isconfirmed to Ensure Bar Completion

When working with values during live trading, use barstate.isconfirmed to ensure you only act once a bar is fully closed.

✅ Example:

if barstate.isconfirmed and crossover(shortMA, longMA)
    strategy.entry("Buy", strategy.long)

This guarantees that signals are only generated after the bar finalizes — just like real trading.


3. Handle request.security() Carefully

Fetching higher timeframe data can cause repainting if you use unconfirmed values.
You can fix this using a wrapper function to pull stable data:

✅ Non-repainting request.security() usage:

//@version=5
indicator("Non-Repainting Security", overlay=true)

f_security(_sym, _res, _src) =>
    request.security(_sym, _res, _src[barstate.isconfirmed ? 0 : 1])

htfHigh = f_security(syminfo.tickerid, "D", high)
plot(htfHigh, color=color.red)

This ensures you use the previous confirmed value on a still-forming bar.


4. Reference Stable Values Instead of Realtime Data

Whenever possible, base your calculations on previous bar data:

✅ Example:

previousClose = close[1] // Use previous close instead of live close

This simple trick avoids live price fluctuations from affecting your calculations.


5. Implement Alerts with Care

If your script triggers alerts during an open bar, you may get multiple false signals.

✅ Correct alert structure:

if barstate.isconfirmed and crossover(shortMA, longMA)
    alert("Buy Signal", alert.freq_once_per_bar_close)

This ensures alerts only fire after a bar closes — exactly like a live trader would experience.


Quick Best Practices Summary

✅ Freeze important values at entry (stop-loss, take-profit)
✅ Use barstate.isconfirmed in your trading logic
✅ Handle request.security() calls carefully
✅ Reference previous bar values where possible
✅ Only trigger alerts once per closed bar
✅ Disclose repainting risks clearly if you publish your scripts


Final Thoughts

Repainting isn’t a bug, it’s simply how Pine Script works if you don’t code carefully. The key is asking yourself:

"Would I have seen this information live, at the time of the trade?"

If not, your script may be repainting dangerously.

Building realistic strategies means being honest about how trades behave in real markets. Mastering anti-repainting techniques is essential if you want your systems to survive beyond backtesting and actually succeed when live money is on the line.


Start your free trial

Try CrossTrade for 7 days.

Sign Up

Quick Sources: