MMTMMT Docs

Start here

Learn the shape of a Scripting v3 indicator and build one step by step.

Scripting v3 scripts have two parts: declarations describe what the script contains, and handlers say what happens when market data changes.

You do not need to learn the entire language before making something useful. If you have used Scripting v2, begin with what's new in v3. Otherwise, start with your first indicator, then use the guides as individual concepts become relevant.

How a script is organised

A script starts with script "Name". Everything after it is a top-level declaration. Loose calculations cannot sit between declarations; put executable statements inside a function or handler.

DeclarationWhat it defines
typeA reusable value with named fields and optional methods.
fnA reusable calculation or action. It runs only when called.
inputA setting people can change from the indicator UI.
dataA market-data subscription and the name used to read it.
setupA value or family whose shape is decided when the script starts.
stateA value initialised once and kept between handler calls.
window and paneWhere a script's visuals appear.
plot and fillVisual outputs that handlers can update.
onA handler that runs in response to one subscription event.

You can group related input, data, state, setup, plot, fill, pane, and alert declarations inside parentheses, with one declaration per line. Grouping is optional and does not change how the script works. The formatter aligns = signs on consecutive single-line rows.

script, type, window, fn, and on declarations cannot be grouped this way.

A complete script shape

script "Previous Hour Range"

type PriceRange {
  high: float
  low: float

  fn midpoint() -> float {
    return (self.high + self.low) / 2.0
  }
}

fn makeRange(high: float?, low: float?) -> PriceRange {
  return PriceRange {
    high: high ?? 0.0
    low: low ?? 0.0
  }
}

data chart = subscribe(data.ohlcv)
data h1 = subscribe(data.ohlcv, timeframe: tf("1h"))

input (
  lineColor = input.color(color.orange, title: "Line Color")
)

state previousHour = PriceRange { high: 0.0, low: 0.0 }

plot (
  highLine = plot.line(title: "Previous H1 High", color: lineColor, style: linestyle.step)
  midLine = plot.line(title: "Previous H1 Midpoint", color: color.gray, style: linestyle.step)
  lowLine = plot.line(title: "Previous H1 Low", color: lineColor, style: linestyle.step)
)

on h1.close {
  previousHour = makeRange(h1.high, h1.low)
}

on chart.update {
  if previousHour.high > 0.0 {
    highLine.plot(previousHour.high)
    midLine.plot(previousHour.midpoint())
    lowLine.plot(previousHour.low)
  }
}

Read it from top to bottom:

  1. PriceRange defines the data that belongs together and a method that works with it.
  2. makeRange defines reusable logic. Declaring it does not call it.
  3. The script declares two data subscriptions, one user setting, persistent state, and three plots.
  4. The hourly close handler creates a confirmed range and saves it.
  5. The chart update handler reads that state and draws it.

Types and functions are definitions, not background tasks. A type value is created only when a PriceRange { ... } expression runs. A function runs only where the script calls it, such as makeRange(...) or previousHour.midpoint().

What happens when a script starts

Before market-data handlers run, the script prepares its inputs, subscriptions, windows, panes, plots, fills, and setup values. Each state initializer runs once. State then remains available across historical bars, live updates, and calls to different handlers.

The loaded history is processed first. After that, the same handlers continue responding to live market data. This is why the same script can build its indicators from history and then continue updating without separate historical and live code paths.

Handlers and when they run

Period-based subscriptions can use these handlers:

HandlerWhen it runsTypical use
on source.openWhen a new source period begins.Reset or initialise work for the new period.
on source.updateWhen that subscription advances. It can run repeatedly for a forming live period.Update live calculations and visuals.
on source.closeOnce when a source period is confirmed.Save final values and produce confirmed signals.

A data.trades subscription instead uses on tape.trade(tr), which runs once for each trade and exposes fields such as tr.price, tr.size, and tr.isBuy.

A script can have many handlers. Each subscription can own its logic, and the same period subscription can have separate open, update, and close handlers. There can be one handler for each subscription-and-event combination.

When handlers need to communicate, store the shared result in state. In the example, on h1.close writes previousHour, and later chart updates read it. This is usually clearer than putting every calculation into one large chart handler.

For detailed examples and source-specific event support, continue with data and update events. For live-versus-confirmed behaviour, read repainting and confirmed values.

On this page