TradingCharts

Execution Model

A ChartScript script runs once per bar across the chart's visible history (and continues per-tick on the forming bar). Understanding this loop is essential — it is why every variable is…

The main loop

Conceptually the runtime does this:

for i = 0 … barCount - 1:
    makeBarState(i)
    runBody()                 // every top-level statement in order
    collectOutput(i)
  • i = 0 is the oldest bar in the loaded history, i = barCount - 1 is the current (possibly forming) bar.
  • Every top-level statement runs on every iteration. A plain = assignment is therefore re-computed every bar.
  • var initializers run only on the first bar they are reached; var targets then survive across iterations.
  • Visual output (plot(), plotshape(), drawings) are collected into per-bar arrays that the host renders after the loop finishes.

Series access during the loop

When the body is at bar i, close returns the close of bar i. close[1] returns the close of bar i − 1, close[2] of bar i − 2, and so on. The runtime maintains a per-series ring buffer that grows automatically up to the deepest [n] the script ever requests.

rsi = ta.rsi(close, 14)
up  = rsi > rsi[1]        // true when RSI rose from the previous bar

On bar 0 the lookback is na (there is no previous bar) and bar-state booleans are false. All [n] accesses with n >= i + 1 evaluate to na.

Historical pass vs live pass

Two phases execute the same body:

  1. Historical pass — bars 0 through the last confirmed bar are iterated once; barstate.isconfirmed is true on each.
  2. Live pass — the forming bar re-runs on every tick with barstate.isconfirmed = false, until it closes and joins the historical pass.

During the live pass the runtime creates a snapshot of all state at the last confirmed bar. Each tick restores the snapshot, runs the body once, and collects the forming bar's output. This is what makes a strategy's "process_orders_on_close = false" market order queue up on the signal bar and fill at the next bar's open — the queue sits in the strategy state carried across the snapshot/restore cycle.

varip variables (see Variable declarations) opt out of snapshot rollback and keep updating as ticks arrive.

Repainting

A script "repaints" when values displayed on confirmed bars change after the fact. In ChartScript this can happen for two reasons:

  • A built-in that reads from [1] or further back inside a function body — the historical pass computes the function with the historical values, but a live-pass re-run uses the forming bar's forming values. Confining request.security(..., lookahead=barmerge.lookahead_off) or using ..[1] eliminates the forward peek.
  • Explicit lookahead (request.security(..., lookahead=barmerge.lookahead_on)) — accepted, but documented as "repaints by design"; use it only when the forward peek is intentional.

See Repainting for the full catalogue.

Side effects and ordering

Statements inside a function body execute top-to-bottom just like at script scope. Function calls are evaluated when they appear — plot(), strategy.entry(), label.new(), and log.info() all fire at the point of the call, so order inside the body determines rendering z-order and strategy order sequence:

if ta.crossover(close, ta.sma(close, 20))
    strategy.entry("Long", strategy.long)
    label.new(bar_index, high, "Long")

if ta.crossunder(close, ta.sma(close, 20))
    strategy.close("Long")
    label.new(bar_index, low,  "Exit", color=color.red)

Series-mode functions

Many library functions carry state across bars (ta.sma, ta.rsi, ta.pivothigh, …). They must be called from the top-level body on every bar — otherwise their internal buffer skips bars and goes out of sync. Calling such a function conditionally will trigger a compile warning:

if close > open
    rsi = ta.rsi(close, 14)   // ⚠ warning: series fn inside conditional

The idiomatic fix is to compute the series unconditionally and gate the consumption:

rsi = ta.rsi(close, 14)
if close > open
    plot(rsi, "RSI on green bars")

(There is no actual gating of plot behind a condition — the typical pattern is plot(cond ? rsi : na).)

Order of evaluation

Within a single statement, operands evaluate left-to-right; function arguments evaluate left-to-right before the call. Short-circuit and/or apply as expected.

See also