What Is Repainting?
Repainting is when a TradingView indicator shows a signal on historical bars that would not have been visible in real time when that bar was forming.
In plain terms: the indicator looks perfect on backtests but fails in live trading.
This is one of the most dangerous bugs in custom indicators — and one of the most common. Many traders have lost money trading signals that were generated by repainting indicators without knowing it.
Why Does Repainting Happen?
There are two main causes:
1. Using security() with lookahead
The request.security() function fetches data from higher timeframes. If you use it incorrectly, it can "look into the future" — using a higher timeframe bar's closing value before that bar has actually closed.
// ❌ This repaints — uses future bar data
htf_close = request.security(syminfo.tickerid, "D", close, lookahead=barmerge.lookahead_on)
// ✅ This does not repaint — uses confirmed bar data
htf_close = request.security(syminfo.tickerid, "D", close[1], lookahead=barmerge.lookahead_off)
2. Using close Instead of a Confirmed Value
When a bar is still forming, close is the current price — not the final close. Any signal calculated using close on the current bar can change before the bar closes.
// ❌ Signal changes as price moves on the current bar
signal = ta.crossover(ta.ema(close, 9), ta.ema(close, 21))
// ✅ Only fires on confirmed closes
signal = ta.crossover(ta.ema(close[1], 9), ta.ema(close[1], 21))
How to Spot a Repainting Indicator
The Replay Test
Use TradingView's Bar Replay feature. Go back in time and play the chart forward bar by bar. If signals appear or disappear as you advance, the indicator repaints.
The Live Watch Test
Add the indicator to a live chart and watch it for several bars as they close. If a signal that appeared during a bar disappears after the bar closes — it repaints.
Code Review
Look for:
lookahead=barmerge.lookahead_oninrequest.security()calls- Signals calculated on
close(current bar) without[1]offset - Use of
varvariables that get recalculated on historical bars
Common Repainting Patterns to Watch For
| Pattern | Issue | Fix |
|---|---|---|
request.security(..., close) | Uses unconfirmed HTF close | Use close[1] or lookahead_off |
Signal on close (no offset) | Changes while bar is open | Add [1] offset or use barstate.isconfirmed |
| Dynamic support/resistance | Recalculates on history | Use var and only update on confirmed bars |
highest(high, n) | Includes current bar | Offset by [1] for historical accuracy |
How to Write Non-Repainting Indicators
Use barstate.isconfirmed
Only trigger signals when a bar has fully closed:
if barstate.isconfirmed
// safe to act on close here
signal := ta.crossover(fast, slow)
Use [1] Offsets Correctly
Reference the previous bar's confirmed value instead of the current forming bar:
confirmed_rsi = ta.rsi(close, 14)[1]
Test on Both Historical and Live Data
A non-repainting indicator should produce the same signal at the same bar whether you're viewing it historically or watching it in real time.
Why This Matters for Custom Indicators
If you're commissioning a custom Pine Script indicator, always ask:
- "Is this indicator non-repainting?"
- "How did you test for repainting?"
- "Does it use
lookahead_onanywhere?"
At TraderOSHQ, every indicator we deliver is explicitly tested for repainting using Bar Replay and live testing before delivery. Non-repainting code is a baseline requirement, not an optional feature.
Summary
Repainting happens when an indicator uses future data — either through incorrect security() calls or signals calculated on unconfirmed bars. It makes backtests look great while failing in live trading.
The fix is: use confirmed bar values, avoid lookahead_on, and always test with Bar Replay.
Need a reliable, non-repainting custom indicator? Get a quote from TraderOSHQ — repainting-free code is guaranteed.