Warmup and memory
Warmup is how far back your script's memory reaches. Declare it and Chartnaut walks that much history before every value it keeps, so each value has converged and does not depend on where your chart started loading. This page covers what warmup guarantees, how to declare it, how it resolves on each timeframe, how much your plan allows and how to tell when you declared too little.
What warmup guarantees
With a warmup of L on a timeframe, every bar's value is computed from a walk that starts at least L buckets before it, from a fixed point on the time grid (Seams and anchors explains the grid). So:
A value never depends on how much history your chart loaded, on a reload, or on scrolling.
The chart and a run over history start each bar's memory from the same point and compute the same value, whether that history is computed in one pass or in chunks.
Every timeframe the script reads resolves its own count from the same declaration. A count such as
warmup(1000)is 1,000 buckets on each of them. A length of time such asw.day()is one day on each of them: 288 buckets on5m, 24 on1h.
A script that keeps state and declares no warmup is refused by the lint (window/warmup-required), because its values would depend on where the run began. A script that keeps no state, such as a plot of ctx.volume, declares nothing.
Declaring it
Call warmup(...) once, at the top level, after your inputs. It can read input handles, so the depth follows the settings a reader picks.
const length = input.number({ id: "length", label: "Length", default: 14, min: 1 });
warmup((w) => w.wilder(length)); // RSI: 50x the lengthIt takes a plain number or a function of w, the builder:
Declaration | What it means |
|---|---|
| A constant count of bars, taken as written |
| A lookback of |
| EMA convergence, 30x the length. The same as |
| Wilder smoothing (RSI, ATR, ADX, RMA), 50x the length |
| A length of time. Units are |
| One day, the same as |
| The larger of two needs: independent pieces of state |
| Stages that feed each other add up: an EMA of an EMA, MACD's signal |
| A depth that depends on a select or a toggle |
| State that never forgets: a running sum, a count, an all-time high |
| A setting scaled by your own factor, such as |
| One end of an |
| The largest or the total of a multi-select, or of one field across an array input's rows: |
Terms combine. A script with a daily reset and an RSI declares warmup((w) => w.max(w.day(), w.wilder(length))). MACD stacks its signal on its slow EMA: warmup((w) => w.sum(w.ema(slow), w.ema(signal))). VWAP picks by its anchor setting:
const calculationMode = input.select({
id: "calculationMode", label: "Reset Time", default: "daily",
options: [
{ value: "consistent", label: "Remain Consistent" },
{ value: "daily", label: "Reset Daily" },
{ value: "weekly", label: "Reset Weekly" },
{ value: "monthly", label: "Reset Monthly" },
{ value: "london", label: "Reset at London Session Open" },
],
});
warmup((w) => w.when(calculationMode)
.is("consistent").then(w.forever())
.is("weekly").then(w.span("7d"))
.is("monthly").then(w.span("31d"))
.otherwise(w.day()));The declaration is the largest need across every piece of state the script keeps. One piece that never forgets makes the whole declaration w.forever().
Choosing the term
Your state | Declare |
|---|---|
A lookback of N bars |
|
An EMA |
|
RSI, ATR, ADX or any Wilder average |
|
Resets every session or every day |
|
Resets weekly or monthly | The longest gap between resets: |
Carries the previous session forward, such as yesterday's value area | The span that covers it: |
Never resets: a running total, a count, an all-time high, a trend flag |
|
No state at all | Nothing |
State that resets on a boundary wants a length of time. A count of bars is a different stretch of time on every timeframe, so the lint suggests a span when a script resets on ctx.session.isStart and declares a count (window/reset-wants-span).
How it resolves
A declaration resolves separately on each timeframe the script reads, against the settings the reader picked.
A plain number and a span are taken as written. A span becomes the number of buckets that covers it, rounded up, at least 1.
An expression that reads a number from a setting (
w.of,w.window,w.ema,w.wilder,w.range,w.maxOf,w.sumOf) resolves to at least 50 bars, because a setting can carry a number nobody chose, such as0. Aw.whenthat only chooses between spans and constants is taken as written.Everything is capped at the viewer's indicator memory, covered below.
w.day() on each timeframe:
Timeframe | Bars |
|---|---|
| 1,440 |
| 288 |
| 96 |
| 24 |
| 6 |
| 1 |
The warmup also sets how wide each stretch of the grid is, and stretches are aligned to UTC midnight. With w.day(), every intraday timeframe's stretches start at UTC midnight, so the chart and every timeframe handle share the same boundaries. That shared grid is what keeps values identical whether history is computed in one pass or in chunks, on any chart and at any time.
The settings dialog shows what the declaration means on the chart's timeframe, for example Remembers 288 bars on 5m (1 day).
Why smoothed indicators need twice as much
What an average still remembers of its starting point after L bars is (1 − k)^L. An EMA uses k = 2 / (n + 1). Wilder smoothing, used by RSI, ATR and ADX, uses k = 1 / n, half as large, so its memory fades half as fast and it needs twice the bars to forget its start by the same amount. That is why w.ema is 30x and w.wilder is 50x. The shipped indicators follow this: RSI declares w.wilder(length), ADX stacks two Wilder stages as w.sum(w.wilder(period), w.wilder(period)), and Keltner Channels takes w.max of its EMA and its ATR.
Memory limits by plan
Your plan sets indicator memory: the most buckets a script can hold for one timeframe. It bounds warmup, retain, and the history loaded behind every timeframe handle.
Plan | Indicator memory |
|---|---|
Free | 2,500 buckets |
Starter | 5,000 buckets |
Pro | 5,000 buckets |
Ultra | 10,000 buckets |
It counts buckets, so the span it covers depends on the timeframe. For a market that trades around the clock:
Timeframe | 2,500 buckets | 5,000 buckets | 10,000 buckets |
|---|---|---|---|
| 1.7 days | 3.5 days | 6.9 days |
| 8.7 days | 17 days | 35 days |
| 3.4 months | 6.8 months | 13.7 months |
| 1.1 years | 2.3 years | 4.6 years |
| 6.8 years | Every bar since 2015 | Every bar since 2015 |
Markets closed at weekends stretch each span further. Free and Starter also hold three years of history, so a daily handle on those plans reaches three years back whatever the memory limit says. See Plans for the rest of each plan.
The limit is the viewer's. A script you share resolves against the plan of whoever runs it, and a definition run over history resolves against the plan of the account that started it.
What w.forever() means on your plan
w.forever() means as far back as the plan allows, back to the instrument's first bar. Where your memory limit reaches the start of the history you hold, as it does on daily charts on every plan, a never-forgetting value is exact: a running total is the total over all of that history.
Where it does not, on intraday timeframes, the value counts from a fixed point on the grid between one and two memory limits back, and that point moves forward one limit at a time. A cumulative line such as On-Balance Volume on a 5m chart on Pro therefore restarts its origin every 5,000 bars, and its level steps at those instants. Its shape between steps is exact. A running high stays correct as long as the high is inside the window. For an extreme or total over a longer span, use ctx.summary, which has no memory limit: Summaries and long windows.
Because w.forever() follows the plan, the same never-forgetting script can read differently on a Free account and an Ultra account on intraday timeframes. When your state can be reset on a boundary, resetting it and declaring the span between resets removes that dependence.
Asking for more than your plan allows
Over-declaring is never an error. The script runs with your limit and tells you:
In the builder, the advisory
window/clamped-to-capnames the effective values: the declared warmup/retain for 1h exceeds your plan's 5,000-bucket cap and was clamped. Effective warmup 5000, retain 64.On the chart, the indicator shows "History capped": This script asked for more 1h history than your plan's 5,000-bucket limit allows, so it measures from a date. Below Ultra it offers an upgrade.
In the script,
history.truncatedistruewithreason: "cap".
The other ways history comes up short read the same way, with their own reason:
| What the chart says | Means |
|---|---|---|
| History capped | You declared more than your memory limit |
| History limited by your plan | Your plan's history window stops there |
| History starts at this instrument's first bar | Nothing earlier exists |
| History truncated | The platform's data starts on 2 February 2015 |
Read it before stating an extreme:
const daily = timeframe("1d");
export function onBar(ctx) {
const h = daily.history;
const label = h.truncated ? `high since ${new Date(h.from * 1000).toISOString().slice(0, 10)}` : "all-time high";
}How to tell warmup was not enough
Too little warmup does not break a script. It leaves some of the starting point in each value, and that shows in one place: the grid boundaries, where every value's starting point moves forward. Signs:
A line takes a small step at regular intervals, one warmup's worth of bars apart, where price did nothing unusual.
The same indicator at a higher period steps more than at a lower one.
A level or swing that resets daily moves at those instants on a 5m chart, because it declared a count shorter than a day.
To check, double the warmup and compare. If confirmed values move beyond the last digit you care about, the smaller number was short. Declare the named term rather than a number picked by eye: w.ema and w.wilder leave nothing visible.
The lint catches the common mismatches, all on Lint and error codes:
state/never-forgets: a running sum, count or high declared with a count. Its value would mean "since the grid point" rather than "since the instrument began". Declarew.forever(), or reset it and declare the span between resets.window/forever-on-decaying:w.forever()on state that only fades fetches your whole memory limit for no gain. Declarew.emaorw.wilder.window/decay-under-factor: an EMA or Wilder average whose length is readable from the script, declared under 30x or 50x.window/reset-wants-span: state that resets onctx.session.isStartdeclared as a count. Declarew.day()or a span.
Warmup and retain
retain(...) answers a different question: how many past buckets stay readable through a timeframe handle's bars(n). The maximum of the last 250 daily highs needs retain({ "1d": 250 }) and no warmup, because nothing accumulates. Unset, 64 buckets of each timeframe stay readable.
retain(64); // every timeframe
retain({ "1d": 250, "1h": 500 }); // per timeframe
retain((r) => r.span("20d")); // 20 days of whichever timeframe is read
retain((r) => r.max(r.of(lookback), 64)); // follows a setting
retain((r) => r.forever()); // as far back as the plan allowsBoth are capped by your memory limit. The details are on Timeframe handles and runOn.
