MMTMMT Docs

Execution Model

How scripts are initialized and evaluated across history and realtime updates.

Lifecycle

Scripts follow this flow:

  1. Create a fresh script context and register globals/constants.
  2. Install plotting helpers and built-ins.
  3. Execute your script once (top-level scope).
  4. Repeatedly call onBar(index) during history/realtime evaluation.

Top-level code is where you define metadata, inputs, and subscriptions.

//@version=2
indicator("My Script", false)
const len = input.int("Length", 20)
const candles = subscribe(data.OHLCV)

function onBar(index) {
    plot("SMA", ta.sma(candles.close(), len))
}

onBar(index) Semantics

onBar(index) receives a relative index from newest bar in the current evaluation window:

  • index = 0 means newest bar in that run
  • index = 1 means previous bar
  • etc.

History execution and realtime execution both use this distance-from-newest convention.

Higher-Timeframe Subscriptions

onBar(index) still runs on the script's primary/chart timeframe.

If you subscribe to a higher timeframe with subscribe(..., { timeframe }), that subscription only advances when its own timeframe rolls. Between those boundaries, accessors like htf.close() keep returning the current higher-timeframe bar.

If you need derived values to advance on the higher-timeframe clock, use subscription.calc(...) on that accessor instead of recalculating from htf.close() inside every lower-timeframe onBar() call.

Using higher-timeframe subscriptions does not change the script's primary execution clock:

  • onBar(index) still advances on the chart timeframe
  • context.timeframe still refers to the chart timeframe
  • global unix(offset?) still refers to the chart series
  • htf.unix(offset?) refers to the subscribed higher-timeframe series

That distinction matters for boundary detection:

//@version=2
indicator("Execution Model HTF Example", false)
const htf = subscribe(data.OHLCV, { timeframe: "30m" })

function onBar(index) {
  const newHtfBarOnChart = unix() === htf.unix()
  const prevHtfBar = htf.unix(1)

  if (newHtfBarOnChart) {
    plot("HTF Boundary", htf.close(), { color: color.orange })
  }
}

New Bar vs Realtime Update

  • isNewBar() is true when current unix timestamp differs from previous evaluation.
  • isRealtimeBar() is the inverse (!isNewBar()).
  • If unix is unchanged, the current bar is updated in-place.

Context State Per Bar

context flags update every bar:

  • context.isFirst
  • context.isHistory
  • context.isLast
  • context.isNew
  • context.isRealtime

See Context for full definitions.

Top-Level Only Calls

These are not allowed inside onBar():

  • indicator(...)
  • subscribe(...)
  • subscription.calc(...)
  • input.*(...)

Important

Calling top-level-only APIs inside onBar() throws an error.

subscription.calc(...) has its own restriction layer as well. Inside a calc callback, the runtime disallows plotting, entities, logging, inputs, additional subscriptions, and chart-bar helper functions. Keep calc callbacks side-effect free and return only numbers or flat object snapshots of numbers. Use NaN for missing numeric values.

Plot Channel Freeze After Warmup

After history warmup, plot channel declarations can be locked. If you create a new plot label after lock, execution throws:

plot channel declarations are frozen after history warmup

Practical rule: declare all labels you will use during history.

Runtime Error Behavior

Uncaught script errors move the script session into an error state. Reloading/recreating resets state and allows execution again.

On this page