---
title: "Repainting"
description: "What repainting is, why it breaks backtests, and why a wrun indicator does not repaint by construction: onBar() sees one bar and nothing later, a higher…"
order: 8
section: "core-concepts"
---

<!-- source: docs/indicators/core-concepts/repainting.md; generated by packages/cli/scripts/gen-indicator-docs.ts, do not edit -->

# Repainting

What repainting is, why it breaks backtests, and why a wrun indicator does not
repaint by construction: `onBar()` sees one bar and nothing later, a
higher timeframe is folded in only after its candle closes, an `interval`
pin is read as of each bar's close, and the chart replays the forming bar
from a snapshot of the state the closed bars left. What can still move a
bar you have already seen on the chart is the window the indicator
computes over and a correction to the data, and this page shows both so
neither surprises you. The guarantee comes from the shape of the model,
not from a flag you remember to set.

## What repainting is

![Repainting: the values on closed bars never change; only the forming bar's value moves as new data arrives, until that bar closes](/wrun/images/diagrams/repaint-forming.svg)

Repainting is when a script's historical values change after the fact. The
line you see today over old bars is not the line the script drew when those
bars were live. A signal that looks like it fired one bar early in backtest
fires one bar late in production, or never. The chart redraws itself once
the future arrives, so your backtest is reading numbers that did not exist
at the time.

That is the whole problem in one sentence: a repainting indicator lies
about the past, so anything you measure on history (win rate, drawdown,
signal timing) is fiction. You cannot trust a backtest you cannot
reproduce.

## Why it happens

Repainting comes from reading data that was not yet available at the bar
you are computing, or from a history that is not the one you ran on. Three
sources, two classic and one specific to a module with state:

- **An unclosed higher-timeframe candle.** A naive 4h lookup on a 1h chart
  hands back the forming candle's live value; history backfills the
  finished number the live chart never had.
- **A future-leaking series.** Anything that depends on a later bar: a
  centered smoother, "highest of the next N bars". History resolves it; live
  it does not exist yet.
- **State that depends on where history starts.** A running total or a bar
  counter begins at the first bar loaded; load more history and every value
  shifts, although no bar looked ahead.

The tell is the same each time: the computation saw something during the
backtest that it could not have seen live, or saw something live that
history will not replay.

## The good news: no lookahead by construction

`onBar()` receives exactly one bar: the inputs of the bar being evaluated,
oldest first. There is no history array to index forward, no `[−1]`, no
way to reach the next bar. A value can only depend on bars at or before
its own, so a mark that appears in history would have appeared live on the
same bar. An indicator cannot break this by accident; there is nothing to
peek at.

The causal-prefix guarantee in one sentence: at bar *t*, every value the
module holds was derived only from bars at or before *t*, so the past never
changes when the future arrives.

`bar.count()` is the one way out, and you opt into it by name: it tells a
bar how many bars the run holds, so a value that reads it (a lagging line
that waits for the bar `shift` bars later) does change when a later bar
arrives. The chart keeps such a file honest by running every bar again
whenever a bar opens, so the live chart always matches a fresh load over
the same bars ([How many bars the run holds](execution-model.md#how-many-bars-the-run-holds)).

## Higher timeframes: confirmed by the fold

![Higher timeframes, bar by bar: a 4h candle spans four 1h bars; with lookahead on, a Pine script on history sees the new 4h close from the first of those bars, while in wrun every 1h bar sees the previous close and the new one arrives on the bar after the 4h candle closes](/wrun/images/diagrams/repainting.svg)

Built inside the module, a higher timeframe comes from the chart's own
bars: bucket bars by `time.bar_open_sec`, remember the running bucket's
close, and fold it into the 4h statistic only when a bar from the **next**
bucket arrives. A 4h candle contributes exactly once, after it closed.
Rerun over history and it matches what it showed live, bar for bar. No
look-ahead, no flag to remember.

```typescript sample=cc-repaint-htf
param("bucket_hours", 4, { min: 1, max: 168, description: "Higher-timeframe bucket, in hours" });
// The confirmed higher-timeframe close: flat across the bucket, stepping only when a candle closes.
output("h4_close", line, overlay, { color: "#7c3aed", width: 2, description: "The most recent fully closed 4h candle, no look-ahead" });
// The developing value, opt-in and clearly labeled: it moves with every bar inside the bucket.
output("h4_developing", line, overlay, { color: "#16a34a", width: 1, description: "The forming 4h candle's running close (repaints by design)" });
output("close", line, overlay, { color: "#94a3b8", width: 1, description: "The chart-timeframe close for comparison" });

let bucketSec: f64 = 14400.0;
let bucket: f64 = NaN; // the bucket the running candle belongs to
let running: f64 = NaN; // the running candle's latest close (developing)
let confirmed: f64 = NaN; // the last CLOSED candle's close

function onStart(): void {
  bucketSec = p_bucket_hours() * 3600.0;
}

function onBar(): void {
  const close = bar.close();
  const b = Math.floor(bar.time() / bucketSec);
  if (b != bucket) {
    // A bar from the next bucket has arrived: the running candle is now closed, and only now is it confirmed.
    if (!isNaN(bucket)) confirmed = running;
    bucket = b;
  }
  running = close;
  if (isNaN(confirmed)) return;
  out_h4_close(confirmed);
  out_h4_developing(running);
  out_close(close);
}
```

### Read the staircase

The purple `h4_close` line is flat across four 1h bars, then steps to a new
level, then holds flat again. That staircase is the visual signature of a
correct, confirmed higher timeframe: the value only changes when a 4h
candle actually closes, and between closes there is no new confirmed
information. The green `h4_developing` line tracks the grey chart close
inside each bucket: that is what a repainting value looks like, and it is
drawn here on purpose so you can see the difference. A cross built on the
green line would not survive into production; a cross built on the purple
one reproduces exactly.

## Requesting a live value is opt-in

Sometimes you genuinely want the forming bucket: a live 4h close ticking in
a readout. In a module that is just the running variable, as above, and it
is a deliberate choice you make by reading `running` instead of
`confirmed`. A developing value is fine for a display; it is the wrong
thing for a signal, because it changes as the bucket fills, so a cross or
threshold built on it will not reproduce on history. Reach for it only when
you want to *show* the live edge, never when you want to *act* on it. A
pinned input's `forming` view is the same kind of value.

## Pins are read as of close

An `interval` pin reads real coarser candles, and the chart applies the
same rule for you: a pinned candle reaches a chart bar only as of that
bar's close, live or historical, so a forming 4h candle never leaks into
the 1h rows under it ([Multi-timeframe](multi-timeframe.md)). The cost is a
value that steps once per pinned candle: the same staircase. Act on a pin
through its default `confirmed` view.

## The forming bar replays from a snapshot

![The forming bar replays from a snapshot: the state after the last closed bar is saved; every update of the forming bar starts from that snapshot, so nothing counts twice; when the bar closes it runs once more and becomes the new snapshot](/wrun/images/diagrams/forming-snapshot.svg)

The forming bar is the one exception to "one call per bar": the chart
evaluates it again as new data arrives, at most about once a second. It
does not re-run history for that. It snapshots the module after the last
closed bar (its memory, every module-level variable, and its drawing
handles) and restores that snapshot before each new evaluation of the
forming bar, so every replay starts from exactly the state the closed bars
left: an accumulator cannot count the forming bar twice, and nothing the
forming bar drew is stacked. When the bar closes, the chart evaluates it
once more as a closed bar, then moves on to the new one.

Nothing in the file has to put its state back for this: the chart
restores the snapshot, every module-level variable and every TA object
included, so the forming bar never depends on a reset of your own.

## What can still change a closed bar

No bar changes because it looked ahead, but on the chart two things can
change a bar you have already seen:

- **The window.** The chart runs an indicator over exactly the bars it has
  loaded, and panning back runs it again over the longer window (so does a
  live gap longer than 250 bars). A value that depends on where history
  starts shifts when more history loads: a running total from the first
  loaded bar, a bar counter, the open of the first bar. A value over a
  fixed window (an SMA, a rolling sum) or reset each session stays put once
  its window is loaded; an EMA, which remembers every bar it has seen,
  moves slightly and converges.
- **Corrected data.** When the chart learns that a closed bar's data
  changed (a late trade folded into a closed bar, a candle the venue
  corrected, an ETF flow revised after the US session), it runs the
  indicator again from its buffers, so history matches the corrected data.

```text
let cvd: f64 = 0.0; // a running sum since the first LOADED bar

// Pan back and the chart runs the Indicator again over the longer window:
// every closed bar's cvd shifts by the delta of the bars that just loaded,
// while a delta summed over a rolling window, or reset each session, keeps
// its values.
```

Run-level renderers and drawings are selected from the finished rows (the
newest ready row that satisfies the kind's rule wins), and they never
change an output's value.

## How to stay repaint-safe

A short checklist:

- **Fold higher timeframes on the next bucket.** The confirmed value is the
  one you act on; the running value is a readout.
- **Read pins confirmed.** A pinned coarse input steps as of each bar's
  close; its `forming` view is a readout, not a signal.
- **Do not act on the still-forming bar.** Its high, low, and close are
  moving until it closes. Gate confirmed signals on settled data.
- **Anchor what you act on.** Act on values over a rolling window or a
  session, not on the level of a total that starts at the first loaded
  bar.

Stick to confirmed folds, confirmed pins and anchored windows and your
backtest will mean something: what you measured on history is what the
indicator would have done live.

## See also

- [Multi-timeframe](multi-timeframe.md) for the full bucket-fold and
  `interval`-pin story, calendar buckets included.
- [Execution model](execution-model.md) for the bar loop, warm-up, and
  where an indicator runs.
