Custom indicators

Indicator API

The full Context, metadata schema, conditional colors, and AssemblyScript subset.

This is the reference for the indicator API: the Context object your calc() function receives, the metadata schema that shapes it, and the AssemblyScript subset you can use.

The entry point

Every indicator defines exactly one top-level function:

function calc(ctx: Context): void

It returns nothing. Outputs are written to ctx.plots. The chart calls it once per bar, oldest first; for the live bar it's called repeatedly as ticks arrive.

Context

MemberTypeDescription
ctx.barBarThe current bar — see below. Read-only.
ctx.barsBarHistoryHistorical ring buffer, max 200 bars. Index 0 = current bar, 1 = previous, etc. Read-only.
ctx.inputsgeneratedYour declared inputs as typed fields. Read-only.
ctx.plotsgeneratedYour declared plots. Writable. Reset to NaN on every call.
ctx.barIndexi320-based bar index. Stable across live ticks of the same bar.
ctx.isNewBarbooltrue on the first call for a bar; false on live ticks of the same bar.

Bar

The current bar's OHLCV plus a timestamp:

declare class Bar {
    readonly open: f64;
    readonly high: f64;
    readonly low: f64;
    readonly close: f64;
    readonly volume: f64;
    readonly timestamp: f64;  // Unix seconds, UTC
}

Timestamp is Unix seconds, UTC. There is no timezone API and no per-period info exposed inside the sandbox — you can't tell whether you're on 1m or 1H from inside calc(). For hour-of-day or day-index logic, use integer math:

const t = <i32>ctx.bar.timestamp;
const utcHour = (t / 3600) % 24;
const utcDay  = t / 86400;

BarHistory

declare class BarHistory {
    readonly count: i32;             // up to 200
    open(barsBack: i32): f64;        // 0 = current bar
    high(barsBack: i32): f64;
    low(barsBack: i32): f64;
    close(barsBack: i32): f64;
    volume(barsBack: i32): f64;
}

Returns NaN when barsBack >= count. Use only for the lookback pattern — incremental indicators must keep state in module-level variables instead.

Inputs and Plots

These two classes are generated from your metadata, so the exact shape depends on what you declared. The editor's autocomplete reflects your current metadata in real time. As a reference, an indicator with one int input and three plots gets:

declare class Inputs {
    readonly length: i32;
}

declare class Plots {
    upper: f64;
    middle: f64;
    lower: f64;
}

For plots that declare a colors array, an additional <name>Color: u32 field appears for selecting the per-bar color index.

Metadata schema

The full set of fields you can declare:

Top level

  • name — display name. Max 50 chars; letters / digits / spaces / - / . / _ / ( / ).
  • shortName — short label shown on the chart. Same charset rules.
  • overlaytrue draws on the price pane; false opens a separate pane below (RSI, MACD).
  • precision — optional, non-overlay only. Number of decimals on the price axis.
  • inputs — see below.
  • plots — see below.
  • bands — optional horizontal reference lines.
  • filledAreas — optional filled zones between two plots or two bands.

Inputs

TypeAS typeNotes
inti32Supports default, min, max.
floatf64Supports default, min, max.
sourcePrice selector (close / open / high / low / hl2 / hlc3 / ohlc4). Resolved by the platform; not exposed inside calc — the selected value is written into bar data instead.

Plots

Only type: "line" is supported (no shapes, arrows, or bar-colorers). Style variants:

  • line, histogram, columns, area, stepLine, circles, cross
  • With breaks: lineWithBreaks, stepLineWithBreaks, areaWithBreaks

Plot fields:

  • color6-char hex only (#RRGGBB). No alpha, no rgb(), no CSS keywords. These crash the chart.
  • linewidth 1–4, linestyle solid | dotted | dashed, transparency 0–100.
  • title, visible, showOnPriceScale, showInDataWindow, showInStatusLine, trackPrice.
  • colors — array of hex colors for conditional per-bar coloring (see below).
  • offset — visual shift in bars. Number for fixed shift, string for input ref ("offset": "displacement"), or negated string for left shift.

Pick lineWithBreaks for intermittent plots — session ranges, signal lines, "only when condition X" outputs. Plain line draws straight across NaN bars, connecting gaps you don't want connected.

Bands and filled areas

Bands are horizontal reference lines: { value, name?, color?, linewidth?, linestyle?, visible? }. Useful for RSI 30 / 70 or any oscillator zero-line.

Filled areas are semi-transparent fills between two plots or two bands: { from, to, color?, transparency?, title? }. Use them between plots for clouds (Bollinger, Ichimoku) and between bands for zone shading (RSI 30–70).

Conditional colors

Adding a colors array to a plot creates a <name>Color: u32 field on ctx.plots that picks which color to use per bar. The static color field is ignored when colors is set; out-of-range indices are clamped.

"hist": { "type": "line", "style": "columns",
    "colors": ["#26A69A", "#B2DFDB", "#EF5350", "#FFCDD2"] }
ctx.plots.hist = value;
ctx.plots.histColor = value >= 0 ? (rising ? 0 : 1) : (falling ? 2 : 3);

AssemblyScript gotchas

AS is TypeScript-shaped but stricter. The traps people hit most often:

  • Strict types. No any, undefined, null, or unions. Use f64 for prices, i32 for counters, bool, void.
  • Untyped numeric literals default to i32. Annotate floats explicitly: let x: f64 = 0.0;.
  • Casts use angle brackets. <f64>(len).
  • No closures. Functions can't capture outer-scope variables.
  • No try / catch / throw, no async, no Promises, no imports.
  • No for..of. Use index-based for loops.
  • Math.min and Math.max take exactly two arguments, not rest args.
  • No dynamic property access (obj[key]), no string manipulation beyond ==, no JSON, no regex.

What you can use:

  • Classes with getters/setters, for / while / if / switch, const / let (no var).
  • StaticArray<T>, Array<T>.
  • Math.{sqrt, abs, pow, min, max, log, exp, floor, ceil, round, PI, E}, NaN, Infinity, isNaN(), isFinite().

Something missing or wrong? Email support@strategytune.com.