MMTMMT Docs

Performance

Keep Scripting v3 indicators responsive as calculations and visuals grow.

Most scripts are fast without special tuning. The clearest implementation is usually the best starting point. Optimise after the calculation is correct and only where repeated work is meaningful.

Reuse work within an update

Calculate a value once when several outputs need it:

pane distancePane = pane(title: "Distance from EMA", height: 0.3)

plot (
  averagePlot = plot.line(title: "EMA")
  distancePlot = plot.line(title: "Distance from EMA", on: distancePane)
)

on chart.update {
  let close = chart.close ?? 0.0
  let average = ta.ema(close, length)
  let distance: float? = average == null ? null : close - average

  averagePlot.plot(average)
  distancePlot.plot(distance)
}

The two outputs share a calculation but not a scale: the EMA is a price and stays on the price pane, while the distance oscillates around zero and lives in its own pane via on: distancePane.

Prefer rolling functions for rolling questions

Functions such as moving averages maintain the history needed for their calculation. Use them instead of rebuilding the same window manually on every update.

Bound collections and visual objects

Choose capacities that match what the indicator can display. Reuse keyed entities or pools when an object represents the same logical level, label, or zone over time.

Subscribe only to data you use

Each extra source adds work and history. Do not add a higher-timeframe, order-book, or volume subscription until the calculation needs it.

For long volume-profile or order-book history, request the timeframe the calculation needs with subscribe(data.volume, timeframe: tf("1D"), history: 30) or the corresponding data.book subscription. This loads source-period history directly. Higher-timeframe volume can have coarser price bins; use the returned binSize when interpreting levels. Prefer summary, POC, value-area, and rolling-profile queries over repeatedly scanning all levels.

Stay inside the step budget

Every script has a limit of 10 million calculation steps per batch of up to 512 bars; while loading history, catch-up runs count as bars too (below). This applies to both historical and live data, on every plan. If a script reaches the limit, it stops with budget_exceeded; edit it to reduce the work before running it again.

While loading history, a subscription finer than the chart makes the script replay on that finer grid. The first fine bar then catches up every older chart bar, running its handlers once per older bar; each of those catch-up runs counts like a bar, so the limit applies per 512 bars or catch-up runs. The time limit for loading a batch of history does not grow with the catch-up runs, so a script that does heavy work on every catch-up run can still stop. When a history replay stops at the limit, the message names the timeframe history was replayed on and how many catch-up runs the stopping bar had. If you add a fine subscription only as a live clock, give it history: 0s so history is not replayed on its timeframe; see finer subscriptions and history replay.

Because the limit is renewed every 512 bars or catch-up runs, the steps the metrics window shows for one evaluation can exceed 10 million when that evaluation ran many catch-up runs. A single handler run that alone takes more than 10 million steps still stops the script.

The limit covers all work across the batch, so repeated work on each bar adds up. Common causes include:

  • Rebuilding a collection on every bar. Reassigning an array or map inside on chart.update copies it each time; keep the collection in state and update it in place.
  • Drawing current-only objects on every historical bar. For objects such as the current key levels, draw under chart.isLast inside on chart.update. Keep historical drawings outside that condition if you want them to remain visible.
  • Nested loops over long windows. Prefer rolling functions and bounded capacities to scanning the whole history each bar.

Confirmed work belongs in on source.close, which runs once per period; the handlers table in getting started lists when each handler runs.

Keep update handlers clear

Update handlers should make repeated work easy to see. Move configuration into declarations and setup, use descriptive intermediate values, and avoid deeply nested expressions that are difficult to inspect.

Measure representative data

Test with the amount and kind of history people will actually load. A short candle-only example cannot reveal the cost of a large depth calculation or thousands of drawings.

On this page