History Reference
In Navi, every expression is evaluated once per bar, producing a time series of values. The [] operator accesses the value that an expression produced on a previous bar — not just built-in variables, but any expression:
let previousClose = close[1]; // close price one bar ago
let twoBarsAgo = high[2]; // high price two bars ago
let prevSma = ta.sma(close, 14)[1]; // SMA value from the previous bar
let prevEma = ta.ema(close, 10)[3]; // EMA value from 3 bars ago
let prevRange = (high - low)[1]; // bar range from the previous bar
let myValue = close * volume;
let prevMyValue = myValue[1]; // previous bar's value of myValueKey concept:
ta.sma(close, 14)[1]does not recompute the SMA — it returns the value that was already calculated when the script ran on the previous bar.
On early bars where insufficient history exists, the result is na. The offset can be a series expression:
let lookback: int = input.int(5, "Lookback");
let pastClose = close[lookback];How Much History a Script Needs
The engine works out how far back a script reaches by reading the script, and keeps exactly that much. A script whose deepest read is close[19] retains twenty bars — not because of a setting, but because twenty is what it uses.
The number is published, so whoever fetches the data knows what to send. From the CLI it is sessionInfo.script.requiredHistory; from the chart and playground it arrives on the same session info. It has three states, because a number alone would not say whether it is an answer:
{"type": "exact", "bars": 20}
{"type": "atLeast", "bars": 1}
{"type": "unknown"}exact — send that many and the values are what they would be with any more. ta.sma(close, 20) reports 20 and means it.
atLeast — a floor. The script carries state between bars, or reads its own position in the feed, so its output keeps moving as more history arrives:
plot(ta.ema(close, 20)); // atLeast, 1 barAn EMA keeps its running value in a var, which survives the bar without any history buffer — so it reads nothing back, and reports 1. Its value still settles over hundreds of bars as the first one's influence decays. How many is a property of the arithmetic rather than of the source, so nothing claims to know; send the floor plus whatever warm-up you consider enough.
unknown — nothing was worked out; send as much as you have.
The engine keeps only what it needs in every case, so a long backfill costs nothing beyond the fetch itself.
When It Cannot Be Worked Out
Some depths cannot be known ahead of time — an index computed from the bars themselves, or a length that is not linear in its inputs:
let n = int(volume) / 1000;
plot(close[n]); // how deep is this? nobody knows until it runsnavi check points at the statement and says why. The engine then keeps only what it could establish, and a read that reaches past that fails with an error naming the depth to declare — it does not quietly return na on some later bar, far from the cause.
Declaring the depth is how you answer:
let length = input.int(20, "Length");
max_bars_back(close, length + 32); // may be an expression over inputsA declaration is a promise from the author about what the compiler could not see, so it is only needed where the compiler said so. Where a bound was proved, the proof already covers every read and a declaration adds nothing.
indicator(max_bars_back: N) says the same thing for the whole script. max_bars_back(v, N) is usually better: it names the series that needs the depth, and it also caps that series at N, so declaring less than a script reads truncates it.
Next Steps
- Variables & Qualifiers —
var, type qualifiers,na - Collections — array, map, matrix

