Documentation
4. Custom Indicator API Reference
- Created
- Jun 5, 2026
- Updated
- Jun 5, 2026
This is the full ctx surface available to a custom indicator. You author an indicator the same way as a strategy: export default an object with a next(ctx) method (and an optional init(ctx)). next runs once per bar, oldest → newest, and emits plot values with ctx.plot(). State persists across bars, so each bar is O(1).
Write TypeScript or JavaScript — types are stripped, not type-checked. See Authoring Language & Syntax for what is rejected (imports, async/await, returning a Promise from next, TS namespace/decorators).
Shape
export default {
name: "My Indicator",
init(ctx) {
// optional — runs once before the bars. Read config, store on `this`.
this.length = ctx.input("length", 20);
},
next(ctx) {
// runs once per bar
ctx.plot("value", ctx.ta.sma(this.length).push(ctx.source("source", "close")));
}
};
You can keep state on this or use ctx.var. A bare return { next(ctx) { … } } is accepted too.
next(ctx) — runs once per bar
ctx.bar— this bar's{ time, open, high, low, close, volume }.ctx.index— 0-based bar number.ctx.isLast/ctx.isConfirmed(forming vs. closed).ctx.input(key, fallback)— your configured inputs (Settings → Inputs).ctx.source(key, fallback)— this bar's price: an OHLC source ("close","open","high","low","hl2","hlc3","ohlc4") or another indicator"ind:<id>#<output>"(e.g."ind:rsi#value").ctx.plot(id, value, { color? })— emit a plot.idmust match a plot in Settings → Plots;null= no value (warmup).ctx.var(key, () => init)— persistent scalar.ctx.series(key)— history buffer (s.set(v)writes this bar,s(n)readsnbars back).ctx.na(v)/ctx.nz(v, fb)— null/NaN helpers.ctx.ta— streaming math (stateful; call once per bar with.push):
ctx.ta.ema(len) ctx.ta.sma(len) ctx.ta.wma(len) ctx.ta.smma(len) // .push(value)
ctx.ta.highest(len) ctx.ta.lowest(len) ctx.ta.sum(len) ctx.ta.stdev(len) // .push(value)
ctx.ta.atr(len) // .push(bar) ctx.ta.vwma(len) // .push(value, volume)
Each returns this bar's value or null during warmup. Pass a stable string key as the 2nd argument when calling the same helper twice (e.g. ctx.ta.ema(12, "fast")).
init(ctx) — runs once before the loop (optional)
ctx.input(key, fallback)/ctx.style(key, fallback)— read config; store onthis.ctx.useIndicator(id, inputs?)→{ at(n=0) }— consume another indicator.at(n)is its valuenbars back (cursor-bound, no lookahead), ornull.ctx.useMultiIndicator(id, inputs?)→{ at(field, n=0) }— multi-line (e.g.macd→"macd"/"signal"/"histogram").ctx.usePattern(kind, params?)→{ firedAt(n=0), recent(lookback=1) }— chart patterns (bullFlag,doubleTop,headShoulders, …). Store the handle onthis; read it innext().
See Composing Indicators & Patterns for consumption details.
Rules
- A throw inside
next()nulls just that bar and continues — it never aborts the series. - Use the editor's Template menu for ready examples (Moving Average, ATR, Bollinger Bands, MACD, composition).
Legacy (schemaVersion 1) indicators use a whole-array body:
return { values: { <plotId>: (number|null)[] }, colors? }with(bars, input, math, source). Existing ones keep working; new indicators use the per-bar form above.
Next steps
Apply the same engine to trading logic.
Next: Writing Strategies