Skip to content

Charts

Indicators

The built-in library, their inputs, and how to register your own in 20 lines.

Read this page as Markdown, or take the whole documentation in one file.

Adding One

const ema = chart.addIndicator("ema", { length: 21 });
const rsi = chart.addIndicator("rsi");                       // its own pane
const macd = chart.addIndicator("macd", { fast: 12, slow: 26, signal: 9 });

An indicator declares its own inputs, outputs, colors and pane, so the second argument is optional and any input you leave out takes its default. Outputs become ordinary series, so everything on Series types applies to them: they appear in the legend, they autoscale, they can be moved to another scale or pane, and they are hidden and shown like any other series.

The instance you get back is the handle for changing and removing it:

ema.setInputs({ length: 50 });      // recomputes and relabels
ema.setVisible(false);
ema.remove();

ema.id;        // instance id
ema.def;       // the definition: name, inputs, outputs, pane, levels
ema.paneId;    // where it was placed
ema.series;    // Map<outputKey, SeriesModel>
ema.inputs;    // resolved inputs, defaults filled in

The Built-In Library

There are 51 as of version 0.2.0: 18 that draw on the price pane and 33 that take a pane of their own. That is comparable to Highcharts Stock, which ships about 50, and short of TradingView Advanced Charts, which ships roughly 110, where the long tail is mostly further variations on what is here. Anything missing is a registerIndicator call, and the engine — inputs, multiple outputs, bands, levels, own panes, incremental recompute — has no privileged path for the built-ins.

The tables below are generated from the registry. listIndicators() is the same list at runtime, and it is the one to read if you build your own menu.

Overlays on the Price Pane

Id Name Inputs Outputs
sma Moving Average length (20), source sma
ema Exponential Moving Average length (20), source ema
wma Weighted Moving Average length (20), source wma
vwma Volume Weighted Moving Average length (20), source vwma
hma Hull Moving Average length (9), source hma
dema Double Exponential Moving Average length (20), source dema
tema Triple Exponential Moving Average length (20), source tema
bb Bollinger Bands length (20), mult (2), source upper, basis, lower
keltner Keltner Channels length (20), mult (2), atrLength (10), source upper, basis, lower
donchian Donchian Channels length (20) upper, basis, lower
envelope Envelope length (20), percent (2.5), source upper, basis, lower
atrbands ATR Bands length (14), mult (2), source upper, lower
linreg Linear Regression Channel length (100), mult (2), source upper, basis, lower
ichimoku Ichimoku Cloud conversion (9), base (26), spanB (52), displacement (26) conversion, base, spanA, spanB, lagging
psar Parabolic SAR step (0.02), max (0.2) sar
supertrend Supertrend length (10), mult (3) supertrend
pivots Pivot Points period (day, week, month), method (classic, fibonacci) r3, r2, r1, p, s1, s2, s3
vwap VWAP anchor (session, week, month, all) vwap

Indicators in Their Own Pane

Id Name Inputs Outputs Levels
rsi Relative Strength Index length (14), source rsi 30, 70
stochrsi Stochastic RSI rsiLength (14), stochLength (14), k (3), d (3), source k, d 20, 80
macd MACD fast (12), slow (26), signal (9), source hist, macd, signal 0
ppo Percentage Price Oscillator fast (12), slow (26), signal (9), source hist, ppo, signal 0
stoch Stochastic k (14), smooth (3), d (3) k, d 20, 80
willr Williams %R length (14) r −80, −20
cci Commodity Channel Index length (20) cci −100, 100
cmo Chande Momentum Oscillator length (9), source cmo −50, 50
roc Rate of Change length (9), source roc 0
mom Momentum length (10), source mom 0
trix TRIX length (15), source trix 0
dpo Detrended Price Oscillator length (21), source dpo 0
ao Awesome Oscillator fast (5), slow (34) ao 0
uo Ultimate Oscillator fast (7), middle (14), slow (28) uo 30, 70
adx Average Directional Index length (14), smooth (14) adx, plus, minus 25
aroon Aroon length (14) up, down 30, 70
vortex Vortex Indicator length (14) plus, minus 1
volume Volume ma (20) volume, ma —
rvol Relative Volume length (50) rvol 1
cumvol Cumulative Volume length (20) sum —
obv On Balance Volume — obv —
ad Accumulation/Distribution — ad —
pvt Price Volume Trend — pvt —
mfi Money Flow Index length (14) mfi 20, 80
cmf Chaikin Money Flow length (20) cmf 0
chaikinosc Chaikin Oscillator fast (3), slow (10) osc 0
eom Ease of Movement length (14), divisor (10000) eom 0
atr Average True Range length (14) atr —
stdev Standard Deviation length (20), source stdev —
hv Historical Volatility length (10), annual (365) hv —
bbw Bollinger Bandwidth length (20), mult (2), source bbw —
delta Volume Delta method (auto, aggressor, estimate) delta 0
cvd Cumulative Volume Delta method (auto, aggressor, estimate) cvd —

delta, cvd and rvol are covered in detail on Order flow; they read the aggressor volumes on the bars when the feed supplies them and say so in the tooltip when they had to estimate instead.

The source input accepts close, open, high, low, hl2, hlc3 and ohlc4.

Enumerate the registry at runtime rather than hard-coding the list:

import { listIndicators, getIndicator } from "@ortex-charts/financial";

for (const def of listIndicators()) {
  console.log(def.id, def.name, def.pane, def.inputs.map((i) => i.key));
}

getIndicator("rsi").levels;   // [30, 70]
An indicator in 20 lines@ortex-charts/financial
Loading an indicator in 20 lines…
Register your own indicator and it appears in the menu, the legend and the saved layout like a built-in one.

Open this example with its source

Placement and Color

chart.addIndicator("rsi", { length: 14 }, {
  pane: "separate",          // "overlay", "separate", or an existing pane id
  paneHeightRatio: 0.25,
  colors: { rsi: "#A78BFA" },
  seriesOptions: { lineWidth: 1, lastValueVisible: false },
  sourceSeriesId: comparisonSeries.id,
});
  • pane overrides where the definition wanted to go. Passing an existing pane id puts two indicators in one pane, which is how RSI and Stochastic end up together.
  • colors overrides output colors by output key; unset, each output takes the color its definition derives from the theme, so a theme switch recolors it.
  • seriesOptions is applied to every output series.
  • sourceSeriesId computes the indicator from another bar series instead of the main one, which is how you put an RSI of a comparison symbol on the chart.

How Live Recomputation Works

Indicators recompute whenever the series they read from changes. On a live tick, a full pass over ten years of minute bars for every indicator would be the dominant cost, so an indicator may declare a lookback: the number of bars its last value depends on, to the precision that matters on screen.

When lookback is set, a tick recomputes only the last max(500, lookback) bars and patches the final value. This is verified against a full recompute to within 1e-6 in the test suite. Indicators that carry state from the beginning of the data — obv, cvd, vwap, cumvol — leave lookback unset and recompute fully, which is correct and slower.

The practical consequences:

  • A moving average, RSI or MACD on 100,000 bars costs the same per tick as on 1,000.
  • Cumulative indicators are the ones to watch on very long histories with a fast feed.
  • Changing an input forces a full recompute, which is what you want.

Writing Your Own

An indicator is one object. compute receives the aligned main-series columns and returns one Float64Array per output key, and everything else — the legend label, the settings dialog, the pane, the level lines, the band fill, the layout entry — follows from the declaration.

import { registerIndicator, sourceColumn, type IndicatorDef } from "@ortex-charts/financial";
import { rollingMean, rollingStd } from "@ortex-charts/math";

const zscore: IndicatorDef = {
  id: "zscore",
  name: "Z-Score",
  short: "Z",
  pane: "separate",
  levels: [-2, 0, 2],
  lookback: (inputs) => 10 * Number(inputs.length),
  inputs: [
    { key: "length", name: "Length", type: "number", default: 50, min: 2, max: 2000, step: 1 },
    { key: "source", name: "Source", type: "select", default: "close", options: ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4"] },
  ],
  outputs: [{ key: "z", title: "Z", plot: "line", color: (theme) => theme.palette[2] }],
  compute({ columns, inputs }) {
    const src = sourceColumn(columns, inputs.source);
    const length = Number(inputs.length);
    const mean = rollingMean(src, length);
    const sd = rollingStd(src, length);
    const z = new Float64Array(src.length);
    for (let i = 0; i < src.length; i++) z[i] = sd[i] > 0 ? (src[i] - mean[i]) / sd[i] : NaN;
    return { z };
  },
};

registerIndicator(zscore);

chart.addIndicator("zscore", { length: 100 });

Registering it also puts it in the toolbar's indicator menu and lets it survive in a saved layout, because both read the same registry.

The Definition Fields

Field Meaning
id Registry key, and what a layout stores.
name Full name in the menu and the settings dialog.
short Legend label prefix, for example EMA. Inputs are appended automatically.
pane "overlay" for the price pane, "separate" for its own.
levels Constant value lines drawn in a separate pane, such as 30 and 70 for RSI.
lookback Bars the last value depends on, a number or a function of the inputs. Unset means full recompute.
priceFormat { type, precision } for the axis and legend of the outputs.
inputs Declarations the settings dialog is generated from.
outputs One per returned column.
compute The calculation.

Input Declarations

{ key: "length", name: "Length", type: "number", default: 20, min: 1, max: 2000, step: 1 }
{ key: "source", name: "Source", type: "select", default: "close", options: ["close", "hl2"] }
{ key: "showBands", name: "Show bands", type: "boolean", default: true }

The four types are number, select, boolean and source. The shell builds the settings dialog from these declarations, so an indicator you register gets the same dialog the built-ins have.

Output Declarations

{ key: "hist", title: "Histogram", plot: "histogram", signColors: true }
{ key: "upper", title: "Upper", plot: "band", bandWith: "lower", color: (t) => t.palette[5], lineWidth: 1 }
{ key: "vol", title: "Volume", plot: "histogram", mainDirectionColors: true }
{ key: "sum", title: "Sum", plot: "area", color: (t) => t.palette[4] }

plot is line, histogram, area or band. A band output pairs with another output named in bandWith and the space between them is filled, which is how Bollinger Bands and Donchian Channels get their shading from two ordinary line series. signColors colors a histogram by sign; mainDirectionColors colors it by the main series candle direction.

color is a function of the theme rather than a string, so the indicator recolors itself when the theme changes rather than being repainted in a color that no longer fits.

The Compute Context

compute(ctx) {
  ctx.columns;    // aligned main-series columns: time, open, high, low, close, volume,
                  // plus buyVolume and sellVolume when the feed supplied them
  ctx.length;     // number of bars
  ctx.inputs;     // resolved inputs
  ctx.timeZone;   // the chart time zone, for session-anchored calculations such as VWAP
  return { key: Float64Array };
}

Return arrays of ctx.length. Use NaN for bars where the indicator has no value, which is normally the warm-up period; the renderer treats NaN as a gap and the legend shows nothing rather than a number.

@ortex-charts/math carries the primitives most indicators need: rollingSum, rollingMean, rollingStd, rollingMin, rollingMax, ema, wilderSmooth, pctChange, logReturns, cumulativeReturn and diff. They are all typed-array in, typed-array out.

What Is Not Here

  • There is no scripting language. No Pine Script, no formula editor, no sandbox. Indicators are TypeScript or JavaScript, which is more capable and less safe: an indicator is code you ship, not code your users write. If letting end users author indicators matters to your product, this library does not solve it for you.
  • The library is 51 indicators deep, not 110. Ichimoku, Supertrend, ADX, Parabolic SAR, Keltner Channels, Money Flow Index, Williams %R, Aroon and TRIX all arrived in version 0.2.0, but the far end of the Advanced Charts list is not here, and some of it never will be.
  • Indicators of indicators are not declarative. Computing an RSI of a moving average means doing both calculations inside one compute.

The Extras Package

The long tail lives in @ortex-charts/indicators-extra, a package of its own so that the chart you ship only carries the studies your users open: 78 more, 30 overlays and 54 in their own pane, which with the built-in 51 is 129, more than TradingView Advanced Charts' catalog of roughly 110. A build that imports it pays about 11 KB gzip; one that does not pays nothing.

import { registerExtraIndicators } from "@ortex-charts/indicators-extra";

registerExtraIndicators(); // once, before the first chart
chart.addIndicator("kama", { length: 10 });

Overlays: Arnaud Legoux MA, Kaufman adaptive MA, Tillson T3, zero-lag EMA, smoothed MA, least-squares MA, McGinley Dynamic, triangular MA, sine-weighted MA, VIDYA, FRAMA, moving-average cross, moving-average ribbon, Guppy multiple MA, Williams Alligator, Chandelier exit, Chande Kroll stop, standard error bands, zig zag, median price, typical price, weighted close, Camarilla, Woodie and Fibonacci pivot points, VWAP bands, anchored VWAP, rolling VWAP.

Own pane: Connors RSI, Laguerre RSI, Fisher transform, Schaff trend cycle, Know Sure Thing, Special K, Coppock curve, relative vigor index, relative volatility index, inertia, balance of power, choppiness index, mass index, Elder Ray, force index, accelerator oscillator, gator oscillator, market facilitation index, true strength index, stochastic momentum index, absolute price oscillator, percentage volume oscillator, Klinger volume oscillator, volume oscillator, volume zone and price zone oscillators, negative and positive volume index, Aroon oscillator, Chaikin volatility, ulcer index, Bollinger %B, Qstick, Williams VIX Fix, Woodies CCI, squeeze momentum, linear regression slope, R-squared, standard error, Chande forecast oscillator, efficiency ratio, center of gravity, polarized fractal efficiency, random walk index, trend intensity index, pretty good oscillator, intraday intensity, volume rate of change, trade volume index, Donchian width, coefficient of variation, normalized ATR, true range, performance, Parkinson and Garman-Klass volatility.

Choosing What Your Users See

A catalog of 129 is for you, not necessarily for your users. Two controls decide what an end user is offered. registerExtraIndicators takes only or except, so only the studies you name exist at all; and the shell's indicators option curates the menu: which ids, pinned favorites, headed groups, and the labels your users know them by.

registerExtraIndicators({ only: ["kama", "stc", "squeeze", "vwapbands"] });

createChartShell(el, {
  indicators: {
    featured: ["sma", "rsi", "squeeze"],
    groups: [
      { title: "Trend", ids: ["sma", "ema", "kama", "supertrend"] },
      { title: "Momentum", ids: ["rsi", "macd", "stc", "squeeze"] },
      { title: "Volume", ids: ["volume", "vwapbands", "kvo"] },
    ],
    otherTitle: "More",
    labels: { rsi: "Strength" },
  },
});

include and exclude narrow the list, an array of ids is a plain allowlist, and false removes the menu. The menu reads the registry when it opens, so a study registered after the shell was created is offered too. Search matches your labels as well as the standard names.