Scripting

Indicators

An indicator script is a small module — a config object plus one calculation function — that plots on the chart exactly like a built-in, and is callable from strategies too. This page covers the contract, plots/panes/params, using a script on the chart and from a strategy, and how built-ins fit in.

The contract

An indicator script exports two things: config (an IndicatorScriptConfig — name, pane, params, plots) and calculate(data, params), which takes the full bar history plus the current parameter values and returns an object of named (number | null)[] series, one per declared plot key. See the Indicator script API reference for the exact types.

export const config: IndicatorScriptConfig = {
	name: 'My Indicator',
	pane: 'separate',
	params: { period: { default: 14, min: 1, max: 500, step: 1 } },
	plots: [{ key: 'values', kind: 'line' }]
};

export function calculate(data: OHLCV[], params: Record<string, number>) {
	const period = params.period;
	const values: (number | null)[] = [];
	let sum = 0;
	for (let i = 0; i < data.length; i++) {
		sum += data[i].close;
		if (i >= period) sum -= data[i - period].close;
		values.push(i >= period - 1 ? sum / period : null);
	}
	return { values };
}

This is the starter template the Editor seeds when you create a new indicator script — a simple moving average, ready to rename and modify.

Plots, panes, params

  • pane — 'overlay' draws directly on the price pane (like a moving average); 'separate' gets its own pane below (like an oscillator).
  • plots — each entry declares a key, a kind ('line', 'histogram', or 'dots'), and optionally a hex color and line width. Every plot's key must match a key in calculate()'s returned object — declare a plot for every series you return, and return a series for every plot you declare.
  • params — Record<string, { default, min?, max?, step? }>; each entry becomes a numeric parameter with a default value and an optional range/step for the chart's settings UI.
export const config: IndicatorScriptConfig = {
	name: 'Close vs SMA %',
	pane: 'separate',
	params: { period: { default: 20, min: 2, max: 500, step: 1 } },
	plots: [
		{ key: 'pct', kind: 'histogram' },
		{ key: 'zero', kind: 'line', color: '#8a8781' }
	]
};

export function calculate(data: OHLCV[], params: Record<string, number>) {
	const period = params.period;
	const pct: (number | null)[] = [];
	const zero: (number | null)[] = [];
	let sum = 0;
	for (let i = 0; i < data.length; i++) {
		sum += data[i].close;
		if (i >= period) sum -= data[i - period].close;
		if (i >= period - 1) {
			const sma = sum / period;
			pct.push(((data[i].close - sma) / sma) * 100);
			zero.push(0);
		} else {
			pct.push(null);
			zero.push(null);
		}
	}
	return { pct, zero };
}

Two plots, two output keys — a histogram for the percent deviation, plus a zero line for a visual reference, each with its own key in the returned object.

On the chart

Once saved, an indicator script renders through the same generic plot adapter every built-in indicator uses: your calculate() output feeds straight into the line/histogram/dots drawing code, and your params become the row's settings fields, in the order you declared them in config.params. Add it to a chart row by typing its name — an upper-cased version of its slug — just like typing a built-in's name (e.g. RSI(14)).

From a strategy

A saved indicator script is also callable from a strategy via ctx.indicator('<slug>', {...}), exactly like a built-in type. The slug is the lower-cased alphanumeric form of the script's name — spaces and punctuation are stripped, not spelled out, so "Close vs SMA %" becomes closevssma. A slug that collides with a built-in indicator type is rejected — built-ins always win — so pick a name that doesn't shadow one of the entries in the indicator catalogue.

Built-ins

Every indicator listed in the indicator catalogue is available to chart rows and ctx.indicator() without writing anything — no Editor tab required. Built-in indicators have no script source (they're platform code, not scripts), so unlike built-in strategies they don't appear in the sidebar or open in the Editor at all; there's nothing to mark read-only. The sidebar's Indicators folder holds only My Indicators — every indicator script there is one you wrote or saved, and it's yours to edit outright. (Built-in strategies do follow the locked, "Save a copy" flow — see Scripting.)