FINTERM · docsOpen terminal
Indicator scripting

Examples

Working snippets for SMA, EMA, Bollinger Bands, Donchian, VWAP, RSI.

Simple moving average

// SMA(20) drawn as a single polyline.
const closes = candles.map(c => c.close);

function sma(values, p) {
  const out = [];
  let sum = 0;
  for (let i = 0; i < values.length; i++) {
    sum += values[i];
    if (i >= p) sum -= values[i - p];
    out.push(i >= p - 1 ? sum / p : NaN);
  }
  return out;
}

const line = sma(closes, 20);

return {
  outputs: [
    { name: 'sma20', value: line[line.length - 1], color: 'azure' },
  ],
  series: [
    { name: 'SMA 20', values: line, color: '#3b82c4' },
  ],
};

EMA

// EMA(21) — single line, NaN warmup.
const closes = candles.map(c => c.close);
const period = 21;
const k = 2 / (period + 1);
const out = [];
let prev = NaN;
for (let i = 0; i < closes.length; i++) {
  const v = closes[i];
  prev = i === 0 ? v : v * k + prev * (1 - k);
  out.push(i >= period - 1 ? prev : NaN);
}
return {
  outputs: [{ name: 'ema21', value: out[out.length - 1], color: 'azure' }],
  series:  [{ name: 'EMA 21', values: out, color: '#1e7a8c' }],
};

Bollinger Bands (20·2)

const closes = candles.map(c => c.close);
const period = 20, mult = 2;
const mid = [], up = [], lo = [];
for (let i = 0; i < closes.length; i++) {
  if (i < period - 1) { mid.push(NaN); up.push(NaN); lo.push(NaN); continue; }
  let s = 0;
  for (let j = i - period + 1; j <= i; j++) s += closes[j];
  const m = s / period;
  let v = 0;
  for (let j = i - period + 1; j <= i; j++) v += (closes[j] - m) ** 2;
  const sd = Math.sqrt(v / period);
  mid.push(m); up.push(m + mult * sd); lo.push(m - mult * sd);
}
return {
  outputs: [
    { name: 'mid',   value: mid[mid.length - 1], color: 'ink'  },
    { name: 'upper', value: up[up.length  - 1],  color: 'down' },
    { name: 'lower', value: lo[lo.length  - 1],  color: 'up'   },
  ],
  series: [
    { name: 'Mid',   values: mid, color: '#8a4b2a' },
    { name: 'Upper', values: up,  color: '#c4554c' },
    { name: 'Lower', values: lo,  color: '#3f8a6e' },
  ],
};

Donchian Channels (20)

const period = 20;
const hi = [], lo = [];
for (let i = 0; i < candles.length; i++) {
  if (i < period - 1) { hi.push(NaN); lo.push(NaN); continue; }
  let h = -Infinity, l = Infinity;
  for (let j = i - period + 1; j <= i; j++) {
    if (candles[j].high > h) h = candles[j].high;
    if (candles[j].low  < l) l = candles[j].low;
  }
  hi.push(h); lo.push(l);
}
return {
  outputs: [
    { name: 'hi', value: hi[hi.length - 1], color: 'up' },
    { name: 'lo', value: lo[lo.length - 1], color: 'down' },
  ],
  series: [
    { name: 'High', values: hi, color: '#3f8a6e' },
    { name: 'Low',  values: lo, color: '#c4554c' },
  ],
};

Cumulative VWAP

// Volume-weighted average of typical price, cumulative across history.
const out = [];
let pv = 0, vv = 0;
for (const c of candles) {
  const tp = (c.high + c.low + c.close) / 3;
  const v = c.volume ?? 0;
  pv += tp * v;
  vv += v;
  out.push(vv > 0 ? pv / vv : NaN);
}
return {
  outputs: [{ name: 'vwap', value: out[out.length - 1], color: 'azure' }],
  series:  [{ name: 'VWAP', values: out, color: '#5b6cc4' }],
};

RSI(14) as a scalar

// RSI is bounded 0–100, so it makes more sense as a chip readout
// than as a polyline overlaid on price. Return a single scalar.
const closes = candles.map(c => c.close);
const period = 14;
let up = 0, dn = 0;
for (let i = 1; i <= period; i++) {
  const d = closes[i] - closes[i - 1];
  if (d > 0) up += d; else dn -= d;
}
let rs = up / (dn || 1);
let rsi = 100 - 100 / (1 + rs);
for (let i = period + 1; i < closes.length; i++) {
  const d = closes[i] - closes[i - 1];
  const u = d > 0 ? d : 0, dd = d < 0 ? -d : 0;
  up = (up * (period - 1) + u) / period;
  dn = (dn * (period - 1) + dd) / period;
  rs = up / (dn || 1);
  rsi = 100 - 100 / (1 + rs);
}
return { name: 'rsi14', value: rsi, color: rsi > 70 ? 'down' : rsi < 30 ? 'up' : 'azure' };