Examples
Bollinger Bands (lookback), EMA (incremental), RSI (bands + filled zones) — reference indicators for the patterns every indicator falls into.
Three classic indicators, each illustrating a different pattern. Bollinger Bands and EMA are complete — copy the metadata and code straight into the editor. The RSI example shows the metadata, with the calculation described in prose.
Bollinger Bands — lookback pattern
Stateless: each call recomputes the SMA and stdev from the last N closes. Live ticks are handled automatically because the recompute reads the current bar's evolving close.
metadata.json
{
"name": "Bollinger Bands",
"shortName": "BB",
"overlay": true,
"inputs": {
"length": { "type": "int", "default": 20, "min": 2, "max": 200 },
"multiplier": { "type": "float", "default": 2.0, "min": 0.1, "max": 10.0 }
},
"plots": {
"upper": { "type": "line", "color": "#2196F3", "linewidth": 1 },
"middle": { "type": "line", "color": "#9C27B0", "linewidth": 2 },
"lower": { "type": "line", "color": "#2196F3", "linewidth": 1 }
},
"filledAreas": [
{ "from": "upper", "to": "lower", "color": "#2196F3", "transparency": 90 }
]
} calc.ts
function calc(ctx: Context): void {
const len: i32 = ctx.inputs.length;
const mult: f64 = ctx.inputs.multiplier;
if (ctx.bars.count < len) return;
let sum: f64 = 0.0;
for (let i: i32 = 0; i < len; i++) sum += ctx.bars.close(i);
const sma: f64 = sum / <f64>len;
let sqSum: f64 = 0.0;
for (let i: i32 = 0; i < len; i++) {
const d: f64 = ctx.bars.close(i) - sma;
sqSum += d * d;
}
const stdev: f64 = Math.sqrt(sqSum / <f64>len);
ctx.plots.upper = sma + mult * stdev;
ctx.plots.middle = sma;
ctx.plots.lower = sma - mult * stdev;
} The early return when ctx.bars.count < len leaves the plots
as NaN, which means "don't draw" — a clean warmup with no dotted-line artefacts.
EMA — incremental pattern
Stateful: the previous EMA value is kept in a module-level variable. On a new bar, commit the previous live value and advance. On live ticks, recompute the tentative current value from the committed previous value and the live close.
metadata.json
{
"name": "EMA",
"shortName": "EMA",
"overlay": true,
"inputs": {
"length": { "type": "int", "default": 20, "min": 2, "max": 500 }
},
"plots": {
"ema": { "type": "line", "color": "#FF9800", "linewidth": 2 }
}
} calc.ts
let prevEma: f64 = 0.0; // committed EMA from last completed bar
let ema: f64 = 0.0; // current value (recalc on each tick)
function calc(ctx: Context): void {
const price: f64 = ctx.bar.close;
const len: i32 = ctx.inputs.length;
const a: f64 = 2.0 / (<f64>len + 1.0);
if (ctx.barIndex == 0) {
ema = price;
} else if (ctx.isNewBar) {
prevEma = ema; // commit previous bar
ema = a * price + (1.0 - a) * prevEma;
} else {
ema = a * price + (1.0 - a) * prevEma; // tentative live value
}
if (ctx.barIndex < len) return; // warmup
ctx.plots.ema = ema;
} The same shape extends to MACD (two EMAs plus a signal EMA, with a four-color histogram
via colors), RSI (incremental average gain and loss), and ATR.
RSI — bands and filled zones
RSI is incremental on the inside and uses bands to mark the overbought/oversold lines plus a filled area between them. This snippet just covers the metadata; the calc would follow the EMA pattern with running averages of gains and losses.
metadata.json
{
"name": "RSI",
"shortName": "RSI",
"overlay": false,
"inputs": {
"length": { "type": "int", "default": 14, "min": 2, "max": 500 }
},
"plots": {
"rsi": { "type": "line", "color": "#7E57C2", "linewidth": 2 }
},
"bands": {
"overbought": { "value": 70, "color": "#787B86", "linestyle": "dashed" },
"oversold": { "value": 30, "color": "#787B86", "linestyle": "dashed" }
},
"filledAreas": [
{ "from": "overbought", "to": "oversold", "color": "#7E57C2", "transparency": 95 }
]
} overlay: false opens RSI in its own pane below the chart. filledAreas can target either two plot ids or two band ids — the type is
auto-detected.
Picking the right pattern
Need more than 200 bars of history? Incremental is the only option. Either way, gate
mutations of committed state on isNewBar.
Or skip writing code and have your connected AI generate one of these for you.
Something missing or wrong? Email support@strategytune.com.