The bar model
Read this page before writing a script that keeps any state.
Closed bars and forming bars
The host calls onBar():
- once for every closed bar.
isNewBar()istrue. - any number of times for the bar that is still forming, on every price
update.
isNewBar()isfalse.
In a backtest or on history every bar is closed, so onBar() runs exactly once
per bar. Live, the last bar is re-run on each tick until it closes.
Series and indexing
A Series is a value per bar. s[0] is the current bar, s[1] the bar
before, and so on. Every ta.* function returns a series, and any series can be
the input of another indicator:
const rsi = ta.rsi(src.close, 14);
const smoothed = ta.ema(rsi, 5); // an indicator over an indicator
Create your own series at top level with series() and write the current bar
with s[0] = value. Only index 0 is writable.
On a forming bar, [0] changes with every tick. [1] and older never change.
Signals computed from [1] do not repaint.
Which state rolls back
After each forming-bar run the host rolls its own state back, so the next tick starts from the last closed bar. Your module's own variables are not rolled back:
| State | Behaves like | Why |
|---|---|---|
src.*, every ta.* result, every series() | Pine var | Host-owned. Committed on a closed bar, rolled back after a forming-bar run |
Module-level let / const, class fields, arrays, maps | Pine varip | Lives in the script's own memory, which is never rolled back |
So count += 1 in a module-level variable counts ticks, not bars, on a live
chart. A backtest does not show the problem, because it runs each bar once.
Three correct patterns:
// 1. A number: keep it in a series. It rolls back for you.
const count = series();
export function onBar(): void {
const prev = na(count[1]) ? 0 : count[1];
count[0] = src.close[0] > src.open[0] ? prev + 1 : prev;
}
// 2. Any other state: only change it on a closed bar.
let lastSignalBar: i64 = -1;
export function onBar(): void {
if (isNewBar() && src.close[0] > src.open[0]) lastSignalBar = barIndex();
}
// 3. Simplest: ignore forming bars entirely.
export function onBar(): void {
if (!isNewBar()) return;
// everything below runs once per bar
}
The stateful helpers Cross, Rising, Falling, BarsSince and ValueWhen
already follow pattern 2, so they are safe to call on every tick.
Indicators stream
Each ta.* call site keeps its own state, and each tick costs one incremental
step. Keep a call site's parameters constant: a length that changes every bar
creates a new indicator each time, and the host stops the script after 256.
na inputs are skipped by recursive indicators (ema, rsi, ...) and make
windowed ones (sma, highest, ...) na while the na is inside the window.
Bar state and context
| Function | Meaning |
|---|---|
isNewBar(), barstate.isConfirmed() | This run is a closed bar |
barstate.isRealtime() | Live data, not history |
barstate.isLast() | The newest bar (every live tick, or the final bar of a backtest) |
barstate.isFirst() | The first bar this script instance saw |
barIndex(), barTime(), timeClose() | Position and times of the current bar |
timeframe.inSeconds(), timeframe.period() | The chart timeframe |
syminfo.ticker(), syminfo.mintick(), syminfo.type() | The symbol |