Skip to main content

Add Trusted Custom Indicators

Expected custom-indicator result rendered as an indicator in a TradeScript chart subpane
The trusted indicator is registered before mount, added by stable id, and verified through its plotted output and instance state.

Version 1 custom indicators are TypeScript modules shipped by the host application. Your build compiles the module; TradeScript registers the imported definition at application startup.

1. Define one stable indicator

Keep the definition in its own .ts file. The id, input ids, and plot keys are storage identities; visible labels can change later.

const deskEma = indicator('Desk EMA', () => {
const length = input.int('Length', 20, {
id: 'length',
min: 1,
max: 500,
});

plot.line(ta.ema(close, length), {
key: 'ema',
title: 'EMA',
color: '#22c55e',
});
}, {
id: 'desk-ema',
compactLabel: 'EMA',
pane: 'price',
});

Checkpoint: code review can identify every input, output, default, and visual key from this module without runtime string inference.

2. Register before creating an instance

const registeredName = registerCustomIndicator(deskEma);

const indicatorId = await chart.addIndicator({
name: registeredName,
paneId: 'candle_pane',
inputs: { length: 34 },
});

Registration is application startup work. Do not compile arbitrary user-provided source inside the chart.

Checkpoint: a green EMA line overlays the candles on the price pane, and the legend gains a Desk EMA row reflecting the length: 34 input rather than the default of 20.

3. Update through the chart API

chart.updateIndicator(indicatorId, {
inputs: { length: 55 },
});

Checkpoint: the plotted line recomputes with the longer period; the legend still shows one Desk EMA row, and no second indicator instance appears.

Verification

  • The indicator appears on the intended pane with an EMA legend row.
  • Changing length updates the line without creating a second indicator.
  • Reloaded settings still use desk-ema, length, and ema.
  • The host validates user-entered values before calling updateIndicator. Custom-indicator input primitives coerce their received overrides; strict catalog validation is available only through updateBuiltInIndicatorProperties for built-ins.
  • Removing the indicator releases its chart-owned state.
Complete typed example
import {
close,
indicator,
input,
plot,
registerCustomIndicator,
} from '@tradescript/pro/sdk/indicators';
import * as ta from '@tradescript/pro/sdk/indicators/ta';
import type { ChartApi } from '@tradescript/pro/sdk/core';

declare const chart: ChartApi;

const deskEma = indicator('Desk EMA', () => {
const length = input.int('Length', 20, {
id: 'length',
min: 1,
max: 500,
});

plot.line(ta.ema(close, length), {
key: 'ema',
title: 'EMA',
color: '#22c55e',
});
}, {
id: 'desk-ema',
compactLabel: 'EMA',
pane: 'price',
});

const name = registerCustomIndicator(deskEma);
const indicatorId = await chart.addIndicator({
name,
paneId: 'candle_pane',
inputs: { length: 34 },
});

chart.updateIndicator(indicatorId, { inputs: { length: 55 } });

Next steps