---
title: "Repainting"
description: "What repainting is, why it breaks backtests, and why an Indicator does not repaint by construction: state() sees one bar and nothing later, a higher timeframe…"
order: 24
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 an Indicator does not
repaint by construction: `state()` sees one bar and nothing later, a
higher timeframe is folded in only after its candle closes, an `interval`
pin is read as of close, and the forming bar is replayed through `reset()`.
The one way to repaint an Indicator is a field `reset()` forgets, and this
page shows what that looks like so you never ship it. kScript (legacy) got
the same guarantee from a confirmed `htf()`; an Indicator gets it from the
shape of the model.

## What repainting is

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. 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.
- **Stale state on the replayed bar.** The forming bar is evaluated on every
  tick; if an evaluation sees leftovers from the previous one, the newest
  value drifts with every tick and settles where history never will.

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

`state()` 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.

## Higher timeframes: confirmed by the fold

On the chart every input is the chart's own interval, so a higher timeframe
is built inside the module: 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
import { input, line, ohlcv, output, overlay, param, time } from "./sdk/declare";
import { in_bar_t, in_close } from "./gen/inputs";
import { emitRow, out_close, out_h4_close, out_h4_developing } from "./gen/outputs";
import { p_bucket_hours } from "./gen/params";

param("bucket_hours", 4, { min: 1, max: 168, description: "Higher-timeframe bucket, in hours" });
input("close", ohlcv.close);
input("bar_t", time.bar_open_sec);
// 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
let close: f64 = NaN;

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

export function state(): i32 {
  close = in_close();
  const b = Math.floor(in_bar_t() / 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;
  return isNaN(confirmed) ? 0 : 1;
}

export function finalize(): void {
  out_h4_close(confirmed);
  out_h4_developing(running);
  out_close(close);
  emitRow();
}

export function reset(): void {
  bucket = NaN;
  running = NaN;
  confirmed = NaN;
  close = NaN;
}
```

### 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.

## Pins are read as of close

On your machine an `interval` pin reads a real coarser feed and the host
applies the same rule for you: a coarser source contributes to a primary
row only as of its candle's CLOSE, live or historical, so a forming 4h
candle never leaks into the 1h rows under it (`multi-timeframe.md`). The
cost is a value that steps once per source candle: the same staircase.

## The forming bar and `reset()`

The forming bar is the one exception to "one call per bar": it re-evaluates
on every tick. The host does not re-run history for that; it snapshots the
module after the last closed bar and replays the revised forming bar into
the snapshot. Because the snapshot holds every module-level variable, the
module's `reset()` is what puts the state back before each replay, and a
stale field is the one way an Indicator repaints:

```text
let barsSeen: f64 = 0.0;
let cvd: f64 = 0.0;

export function reset(): void {
  cvd = 0.0;
  // barsSeen is not reset: the forming bar's replays keep incrementing it,
  // so the newest row reads 1, then 2, then 3 as ticks arrive, and history
  // (which counted it once) will never show those values.
}
```

Every module-level `let` is reassigned in `reset()`, every TA object gets
`.reset()`, and a class you wrote gets a `reset()` of its own. Allocation
does not belong there (the module never frees memory; reset the fields in
place). If a value on the newest bar drifts as you watch it while the
closed bars stay put, `reset()` is the first place to look.

## The host decides which bars the module sees

Run-level renderers and drawings are selected from the finished row set
with no backward scan (the newest ready row that satisfies the kind's rule
wins), and they never change a metric value. On your machine `om metric
series` returns the forming bar as its newest row; bar-evaluated metric
signals compute AS OF the last closed primary bar instead, dropping the
forming rows before the module folds them. The module is the same either
way.

## 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.
- **Do not act on the still-forming bar.** Its high, low, and close are
  moving until it closes. Gate confirmed signals on settled data, or let a
  closed-bar consumer cut the forming bar for you.
- **Reset everything.** Every module-level variable, every TA object, every
  class you wrote. A forgotten field is the one repaint an Indicator can
  have.
- **Keep the primary selector-following and the pins honest.** A pinned
  coarse input is read as of close on your machine; on the chart, pins are
  not honored and every input is the chart's own interval, so build the
  higher timeframe with the fold.

Stick to confirmed folds and a complete `reset()` 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 the
  three hosts.
