Scripting: writing an indicator (JavaScript)
An indicator script defines compute(bars, params, ctx) and returns a list of outputs. This page lists the shapes it can return, where its parameters come from and how the script is stored.
Senzoukria · Documentation · Updated September 2026
Where to find it
- Where
- Scripting page → New indicator
- Entry point
- compute(bars, params, ctx), returning an array of outputs
- Target
- "On the chart" (overlay) or "Own pane"
- Default language
- JavaScript; a script without a recorded language is read as JavaScript
- Budget
- 2 seconds per run, in a worker with no network, no file access and no platform API
What it does
compute() receives the bars, the parameter values and a context, and returns a list of outputs. The renderer knows only those outputs — never which script produced them — so adding an indicator never touches the pipeline.
Time is carried by each bar's bucket timestamp in nanoseconds, the temporal key of the whole footprint pipeline. A point whose value is null is a gap: the renderer lifts the pen instead of drawing a false straight line through a warm-up or a session reset.
Two conventions are worth respecting because the built-in indicators follow them: a level's delta is buy volume minus sell volume and a bar's delta is the sum of its levels; and the tick size comes from the context, never hard-coded.
Output shapes
| Shape | Default | What it changes |
|---|---|---|
| line | width 1 | A series aligned on the bars; null values are gaps. |
| histogram | — | Columns with a positive and a negative colour; a bar with no value emits no point. |
| bands | fill 0.12 | Upper, basis and lower series with a fill opacity clamped between 0 and 1. There is no lib.bands helper in JavaScript: it is built by of.bands in Python, or returned as a plain object. |
| markers | — | Points at a price on a bar, as a dot, an up or down arrow or a square, with an optional label. |
| levels | to = null | Horizontal levels from a bar to another; a null end runs the level to the right edge. |
| table | align left | A floating table in the DOM — it can be read, selected and copied. |
| panel | — | A small panel of label and value rows, optionally coloured. |
Parameters
Parameters can be declared in the editor's panel, but a script may also read a key straight from the source. The parameter reader scans the code statically for the three forms actually written — params.get("key", default), params["key"] and params.key ?? default — plus one-line aliases such as a lambda or an arrow function wrapping params.
That is what feeds the backtest assistant's "Explore every readable parameter" option, which sets up to five axes from the parameters your script really reads, with values around their defaults. It is a proposal in a field you can correct, not a contract: a constructed key or a loop over a dictionary will not be seen.
Limits and pitfalls
- Returning something that is not one of the shapes above is reported as an unrecognised return value rather than drawn.
- A run that finishes without error and plots nothing usually means the values returned are not numbers.
- Scripts live in this machine's local storage; they are not synchronised or backed up for you.
- table and panel are the only shapes that are not drawn on the canvas — they come out as DOM, because a table has to be selectable.
Related pages
This page in other languages
Frequently asked questions
- How do I get my indicator onto the chart?
- Save it, then switch on "Show on the chart". It joins the Indicators panel in the "My scripts" group and is toggled from there like any other indicator.
- Can one script return several outputs?
- Yes. compute() returns an array, and the built-in indicators do the same — a VWAP, for instance, emits a bands output and a line output.
- Why is my moving average missing at the start of the chart?
- Because the warm-up values are returned as null, and null lifts the pen. Drawing through them would invent data that the period has not yet produced.