Custom indicator authoring
There are two different meanings of custom indicator work in this codebase:
- Reconfigure a built-in indicator by cloning the output of
chart.getScripts(), changing input values, and callingchart.addScript(existingKey, clonedDefinition). - Author a net-new indicator key by adding a new Fusion script file under the package source and registering it in the indicator registry.
This page covers the second path.
Read Fusion script authoring conventions first for the shared runtime boundary, helper usage, registry workflow, locale rules, and validation steps.
If you only need to change inputs or wire one built-in script to another, stay on the public runtime path and use Programmatic wiring.
Start from the closest built-in indicator
The source-backed indicator files live under packages/chart/src/fusion-scripts/indicators/.
Use a nearby indicator as your template:
EMA.tsis a good overlay example with one output series and a simple controller.ATR.tsis a good new-pane example and shows the sharedcreateSeriesOutput()helper.KELTNERCHANNEL.tsis a good multi-output example when you need several plotted lines.
Most current files use the shared helper pattern summarized in Fusion script authoring conventions.
Example: author a price-spread indicator
Create a new file such as packages/chart/src/fusion-scripts/indicators/PRICESPREAD.ts.
import type { CoreFusionStatic } from "../../internal-types/fusion";
import type { FusionScriptControllerRuntime } from "../../internal-types/scripts";
import {
createController,
createSeriesLinePlotter,
createSeriesOutput,
defineScript,
} from "../helpers/scriptDefinition";
export default function createPriceSpreadIndicatorScript(FUSION: CoreFusionStatic) {
return defineScript({
title: "priceSpreadTitle",
description: "priceSpreadDescription",
type: "indicators",
newPane: true,
centerZero: true,
inputs: {
OPEN: { type: "series", name: "priceOpen", properties: { def: "o" }, value: null },
CLOSE: {
type: "series",
name: "priceClose",
properties: { def: "c" },
value: null,
},
},
outputs: {
SPREAD: createSeriesOutput("priceSpreadTitle", ["value"], ["SPREAD"]),
},
plotters: [
createSeriesLinePlotter({
dataLink: "SPREAD",
dataField: "SPREAD",
color: "#14b8a6",
width: 1.5,
}),
],
controller: createController(function (this: FusionScriptControllerRuntime) {
this.calculate = function (index: number) {
const open = this.OPEN.getValue(index);
const close = this.CLOSE.getValue(index);
if (open == null || close == null) {
return;
}
this.SPREAD.setValue(index, close - open);
};
}),
});
}
What each part controls
type: "indicators"places the script in the indicator category.newPanedecides whether the script overlays the main chart or opens a separate pane.centerZerois useful for oscillators that should render around zero.inputsdefine the editable fields shown by the runtime. Forseriesinputs,properties.defgives the runtime a default field to auto-bind against the current series, for exampleo,h,l,c, orv.outputsdefine the runtime series objects created for the script. The field names you place there become the controller wrappers you write to, for examplethis.SPREAD.plottersdefine how each output is drawn.controllerowns the actual calculation path.init()is optional.calculate(index)is where you read input wrappers and write output values.
After the file exists, follow Fusion script authoring conventions to register the new key, add locale strings, validate the package, and verify the runtime path.