Overview
What an indicator is, how it runs, and the two patterns every indicator falls into.
StrategyTune custom indicators are small AssemblyScript programs that run in a WebAssembly sandbox on top of the chart. You write the math; the platform handles the chart, memory, the data feed, and the lifecycle. The result is fast, deterministic, and completely isolated from the rest of the page.
What an indicator is
An indicator has two pieces:
- Metadata — a JSON description of the indicator's name, inputs, plots, optional bands, and optional filled areas. This is what the chart UI uses to render settings dialogs and draw lines/columns/areas.
- Code — a single AssemblyScript file with one entry point:
function calc(ctx: Context): void. The chart callscalc()once per bar, oldest to newest, and you write intoctx.plotsto draw on the chart.
The execution model
The chart calls your calc(ctx) function once per bar, oldest →
newest. On any recalculation — timeframe change, scrolling history, changing an
input — all WASM state is wiped and calc() is replayed across history from
scratch.
For the latest (live) bar, calc() is called many times as
price ticks come in:
ctx.isNewBar === true— first call for a bar.ctx.isNewBar === false— subsequent live ticks of the same bar.
ctx.plots is reset to NaN on every call. Never return without setting plots on a live tick — the plot will freeze
mid-bar. Always assign ctx.plots.<name> on every call (except
during warmup, where leaving NaN means "don't draw").
Two computation patterns
Almost every indicator falls into one of two shapes. Picking the right one matters; the wrong one either gives wrong values during live ticks or is needlessly slow.
1. Lookback pattern
Stateless. Each call recomputes the value from the last N bars in ctx.bars. Safe on live ticks because recomputing across history is the same
as recomputing on a tick.
Use it for: SMA, Bollinger Bands, standard deviation, anything that's a function of the last N closes.
2. Incremental pattern
Stateful. Keep a committed value in module-level variables; advance the committed value
only on ctx.isNewBar; on live ticks, recompute the current value from the
previously committed state plus the live price.
Use it for: EMA, RSI, MACD, ATR, anything where today's value depends on yesterday's value.
Don't iterate ctx.bars to recompute an EMA from scratch each call — it's
wrong (early bars don't see enough history) and slow.
The sandbox
- Pure WebAssembly. No DOM, no
fetch, nowindow, no imports. The only environment is theContextobject. - Memory is GC'd. Allocate temporary arrays and objects freely; you don't free them.
- 200-bar history.
ctx.barsis a ring buffer with at most 200 bars. Indicators that need more bars than that have to maintain their own state via the incremental pattern. - No network, no time, no I/O. Indicators are deterministic functions of the bar series and inputs. That's a feature — replays produce identical results every time.
Where you write the code
Custom indicators live in the indicator editor inside the app, with a Monaco code pane,
a metadata pane, and a chat pane. As you type, types tailored to your current metadata
(the exact Inputs and Plots classes you've declared) are
regenerated for autocomplete.
The editor compiles on save: AssemblyScript → WebAssembly, server-side. Compilation errors come back with line numbers pointing at your code. Once the indicator compiles, it registers with the chart and you add it like any built-in.
Don't want to write code? Let an AI write the indicator for you.
Something missing or wrong? Email support@strategytune.com.