# Indicators

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

Section: Charts. Source: https://ortexcharts.com/docs/indicators

## Adding One

```ts
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](https://ortexcharts.com/docs/series) 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:

```ts
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](https://ortexcharts.com/docs/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:

```ts
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]
```

_Live example: [custom-indicator](https://ortexcharts.com/examples/custom-indicator)_

## Placement and Color

```ts
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.

```ts
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](https://ortexcharts.com/docs/shell), 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

```ts
{ 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

```ts
{ 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

```ts
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.

```ts
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.

```ts
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.
