Repainting and confirmed values
Control when signals may change and distinguish confirmed history from the forming candle.
Repainting means a value visible now can change before it becomes final. That is not always a bug: a live candle's high, close, volume, and derived indicators naturally move while the candle is forming. The important part is choosing deliberately between live information and confirmed information.
Confirmed and forming views
Every period subscription exposes both views:
data chart = subscribe(data.ohlcv)
data h4 = subscribe(data.ohlcv, timeframe: tf("4h"))
on chart.update {
let confirmedClose = h4.close
let liveClose = h4.forming.close
}h4.closeis the most recently completed 4-hour close. It remains stable until another 4-hour candle confirms.h4.forming.closeis the current developing 4-hour close. It can change with every incoming update.
The same distinction applies to open, high, low, volume, and supported derived data.
Choose the event that matches the promise
Use on chart.update for visuals that should react during the live candle:
on chart.update {
liveAverage.plot(ta.ema(chart.forming.close, 20))
}Use on chart.close for signals that promise candle-close confirmation:
on chart.close {
let average = ta.ema(chart.close, 20)
let signal: float? = ta.crossover(chart.close, average) ? chart.close : null
confirmedSignal.plot(signal)
}A close-confirmed signal appears later than an intrabar signal, but it will not disappear because the same candle moved back below its threshold before closing.
Higher-timeframe indicators
Higher-timeframe data is where accidental repainting is most common. This plots a stable, stepped 4-hour average on a lower-timeframe chart:
data chart = subscribe(data.ohlcv)
data h4 = subscribe(data.ohlcv, timeframe: tf("4h"))
plot confirmedTrend = plot.line(
title: "Confirmed H4 EMA",
color: color.blue,
width: 2,
style: linestyle.step,
)
on chart.update {
confirmedTrend.plot(ta.ema(h4.close, 21))
}Using h4.close means the current unfinished 4-hour candle cannot influence the line.
If the developing value is useful, show it separately and label it clearly:
plot developingTrend = plot.line(
title: "Developing H4 EMA",
color: color.gray,
style: linestyle.dashed,
)
on chart.update {
developingTrend.plot(ta.ema(h4.forming.close, 21))
}Do not present a forming value as a confirmed signal. The issue is not that forming data is wrong; it answers a different question.
Avoid future information
History indexing always looks backward:
let previousClose = chart.close[1]
let priorH4High = h4.high[1]For higher timeframes, the confirmed view already points to the latest completed period. You do not need to offset h4.close merely to hide the current forming 4-hour candle.
State and the live candle
State updates made while a candle is developing do not accumulate tick after tick into a different confirmed result. Still, live entities, tables, and other visible outputs can respond immediately to forming values. Use stable keys and update the same object when showing live state:
state liveLine = entities.linePool(max: 1)
on chart.update {
liveLine.get("current-high").set(
x1: chart.time ?? time.fromUnixSeconds(0),
y1: chart.forming.high ?? 0.0,
x2: chart.time ?? time.fromUnixSeconds(0),
y2: chart.forming.high ?? 0.0,
)
}For alerts or permanent annotations, prefer confirmed values and a close handler unless intrabar behaviour is explicitly part of the tool.
Drawing at the live edge
chart.isLast is true when the script is live and the current period is the newest one loaded. It is false throughout history replay. It is also always false inside on chart.close, so using it there prevents the drawing code from running and produces warning T449.
Calculate levels on every bar, then draw them under chart.isLast inside on chart.update:
state levels = entities.linePool(max: 8)
on chart.update {
let t = chart.time ?? time.fromUnixSeconds(0)
let priorHigh = h4.high
if chart.isLast && priorHigh != null {
levels.get("h4-high").set(h4.time ?? t, priorHigh, t, priorHigh, extend: extend.right)
}
}The stable key means each live update moves the same line instead of adding one. Use chart.isRealtime when a close handler needs to know whether the script is live at all.
A practical checklist
Before publishing a script, decide:
- Is each signal live or close-confirmed?
- Does every higher-timeframe read use
confirmedorformingintentionally? - Are developing plots named as developing?
- Could a live entity create duplicates instead of updating one stable key?
- Does the script behave sensibly when replayed and when attached to a live chart?
There is no universal rule that a script must never repaint. A live tape tool should update continuously. A candle-close alert usually should not. The script's labels and documentation should make that contract clear.