---
title: "Extra indicators"
description: "The TA library is the core catalog of 52 classes. The ./sdk/ta-plus module adds 30 more with the same shape: construct the class in onStart(), call update()…"
order: 47
section: "functions"
---

<!-- source: docs/indicators/functions/extra-indicators.md; generated by packages/cli/scripts/gen-indicator-docs.ts, do not edit -->

# Extra indicators

The [TA library](ta-library.md) is the core catalog of 52 classes. The
`./sdk/ta-plus` module adds 30 more with the same shape: construct the
class in `onStart()`, call `update()` once per bar. Twenty-six are
textbook indicators the catalog never had; four are Pine's own spellings
of classes the library has in another shape.

The 26 are the mean and percent-rank deviations, Bollinger and Keltner
widths, the volume lines (accumulation and distribution, the Williams
pair, the volume indices, price-volume trend, intraday intensity), the
running extremes, mode, range and centre of gravity, the double and
triple EMAs, the zero-lag EMA, TRIX, Kaufman's efficiency ratio and
adaptive average, the ultimate oscillator, Vortex, Aroon and choppiness.
The four are `Dmi` with its two lengths, `SourceStoch` and `SourceVwap`
over any source series, and `HeikinAshi`. Every class composes the
library's own primitives and follows its three rules: `NaN` until the
class is warm, `NaN` while a non-finite input sits in its window (the
two volume indices carry instead), and no allocation after the
constructor. The window arithmetic under them (`History`, `List`, the
`stats.*` functions, rounding and a seeded random) is on
[Stats, history and lists](stats-history-lists.md).

## Every class in `./sdk/ta-plus`

`update()` returns the primary stream; the extra streams are public
fields you read after the call. "First value" is the bar index of the
first non-`NaN` result on a clean series (bar 0 is the first bar).
Periods below 1 are clamped to 1.

| Class | Construct | `update(...)` | Fields | Definition | First value |
| --- | --- | --- | --- | --- | --- |
| `Dev` | `(period)` | `(x)` | | mean absolute deviation of the last `period` values from their `Sma`: `sum(abs(x - sma)) / period` | bar `period - 1` |
| `PercentRank` | `(period)` | `(x)` | | `100 * count(previous period values <= x) / period`, the current bar excluded from the count | bar `period` |
| `Bbw` | `(period, mult)` | `(x)` | `basis`, `upper`, `lower` | `(upper - lower) / basis` over `Bb(period, mult)`; `NaN` when the basis is 0 | bar `period - 1` |
| `Kcw` | `(period, mult, atrPeriod)` | `(x, high, low, close)` | `basis`, `upper`, `lower` | `(upper - lower) / basis` over `Keltner(period, mult, atrPeriod)`; `NaN` when the basis is 0 | bar `max(period, atrPeriod) - 1` |
| `AccDist` | `()` | `(high, low, close, volume)` | | running sum of `((2 * close - high - low) / (high - low)) * volume`, the term 0 when `high == low` | bar 0 |
| `Wad` | `()` | `(high, low, close)` | | running sum of: `close - min(low, prevClose)` when the close rose, `close - max(high, prevClose)` when it fell, else 0; bar 0 returns 0 | bar 0 |
| `Wvad` | `()` | `(open, high, low, close, volume)` | | `((close - open) / (high - low)) * volume` for this bar, never a running sum; `NaN` when `high == low` | bar 0 |
| `Nvi` | `()` | `(close, volume)` | | starts at 1; when `volume < prevVolume`: `nvi = prev + ((close - prevClose) / prevClose) * prev`, else `prev`; a close of 0 or `NaN`, on this bar or the previous one, keeps `prev` | bar 0 |
| `Pvi` | `()` | `(close, volume)` | | the same with `volume > prevVolume` | bar 0 |
| `Pvt` | `()` | `(close, volume)` | | running sum of `((close - prevClose) / prevClose) * volume`; bar 0 returns 0 | bar 0 |
| `RunningMax` | `()` | `(x)` | | the largest finite `x` seen so far (a non-finite `x` is skipped) | the first finite bar |
| `RunningMin` | `()` | `(x)` | | the smallest finite `x` seen so far (a non-finite `x` is skipped) | the first finite bar |
| `Mode` | `(period)` | `(x)` | | the most frequent value of the window (exact equality); ties go to the smallest | bar `period - 1` |
| `Range` | `(period)` | `(x)` | | `Highest - Lowest` of the last `period` values | bar `period - 1` |
| `Cog` | `(period)` | `(x)` | | `-(sum of x[i] * (i + 1)) / sum(x[i])` for `i = 0` (the newest) to `period - 1`; `NaN` when the sum is 0 | bar `period - 1` |
| `Iii` | `()` | `(high, low, close, volume)` | | `(2 * close - high - low) / ((high - low) * volume)` for this bar; `NaN` when the denominator is 0 | bar 0 |
| `Dema` | `(period)` | `(x)` | | `2 * e1 - e2`, `e1 = Ema(period)` of `x`, `e2 = Ema(period)` of `e1` | bar `2 * period - 2` |
| `Tema` | `(period)` | `(x)` | | `3 * e1 - 3 * e2 + e3` | bar `3 * period - 3` |
| `Zlema` | `(period)` | `(x)` | | `Ema(period)` of `x + (x - x[lag])`, `lag = floor((period - 1) / 2)` | bar `lag + period - 1` |
| `Trix` | `(period)` | `(x)` | | `10000 *` the one-bar change of `Ema(Ema(Ema(ln x)))`; `NaN` when `x <= 0` | bar `3 * period - 2` |
| `Ultimate` | `(p1 = 7, p2 = 14, p3 = 28)` | `(high, low, close)` | | `BP = close - min(low, prevClose)`, `TR = max(high, prevClose) - min(low, prevClose)`, `avg_n = Sum(BP, n) / Sum(TR, n)`, result `100 * (4 * avg1 + 2 * avg2 + avg3) / 7` | bar `p3` |
| `Vortex` | `(period)` | `(high, low, close)` | `plus`, `minus` | `VM+ = abs(high - prevLow)`, `VM- = abs(low - prevHigh)`; `plus = Sum(VM+) / Sum(TR)`, `minus = Sum(VM-) / Sum(TR)` over `period`; returns `plus` | bar `period` |
| `Aroon` | `(period)` | `(high, low)` | `up`, `down`, `osc` | `up = 100 * (Highest(period + 1).bars + period) / period`, `down` likewise with `Lowest`, `osc = up - down`; returns `osc` | bar `period` |
| `Choppiness` | `(period)` | `(high, low, close)` | | `100 * log10(Sum(TR, period) / (Highest(high, period) - Lowest(low, period))) / log10(period)`; `NaN` when the range is 0 | bar `period - 1` |
| `Efficiency` | `(period)` | `(x)` | | `abs(x - x[period]) / Sum(abs(x - x[1]), period)`; `NaN` when the sum is 0 | bar `period` |
| `Kama` | `(period, fast = 2, slow = 30)` | `(x)` | | `sc = (er * (2 / (fast + 1) - 2 / (slow + 1)) + 2 / (slow + 1))^2` with `er = Efficiency(period)`; `kama = prev + sc * (x - prev)`, seeded with `x` on the first bar `er` exists | bar `period` |
| `Dmi` | `(diLength, adxSmoothing)` | `(high, low, close)` | `adx`, `plusDi`, `minusDi` | Pine's `ta.dmi`: `+DI = 100 * Rma(+DM, diLength) / Rma(TR, diLength)`, `-DI` likewise, `DX = abs(+DI - -DI) / (+DI + -DI)` (over 1 when the sum is 0), `adx = 100 * Rma(DX, adxSmoothing)`; a zero smoothed range holds `+DI` and `-DI` (Pine's `fixnan`); a missing bar is skipped the way TradingView skips it (see below); equal lengths give the library's `Adx` on a series without a hole; returns `adx` | `plusDi`, `minusDi` at bar `diLength`; `adx` at bar `diLength + adxSmoothing - 1` |
| `SourceStoch` | `(periodK, smoothK = 1, periodD = 1)` | `(source, high, low)` | `k`, `d` | Pine's `ta.stoch(source, high, low, length)`: raw %K is `100 * (source - Lowest(low)) / (Highest(high) - Lowest(low))` over `periodK`, `k = Sma(smoothK)` of it, `d = Sma(periodD)` of `k`; a flat window follows TradingView (the previous raw %K when the source sits on the window's value, `NaN` otherwise) where the library's `Stoch` reads 0, so fed the close it is `Stoch` on every window with a range; returns `k` | `k` at bar `periodK + smoothK - 2`, `d` at bar `periodK + smoothK + periodD - 3` |
| `SourceVwap` | `(anchor = "")` | `(source, volume, tsMs = NaN)` | | Pine's `ta.vwap(source)`: the library's `Vwap` sums, `sum(source * volume) / sum(volume)` from the anchor (`""`, `"day"`, `"week"`, `"month"`, `"quarter"`, `"year"` or a bucket width in milliseconds; `tsMs` is `bar.time() * 1000.0`, read only with an anchor), over any source; fed `(high + low + close) / 3` it is `Vwap(anchor, "hlc3")` | bar 0 |
| `HeikinAshi` | `()` | `(open, high, low, close)` | `open`, `high`, `low`, `close` | Pine's `ticker.heikinashi`: `close = (open + high + low + close) / 4`, `open = (previous ha open + previous ha close) / 2` (the bar's own `(open + close) / 2` on the first bar), `high = max(high, ha open, ha close)`, `low = min(low, ha open, ha close)`; returns the smoothed close | bar 0 |

A non-finite input (`NaN` or infinite) is handled by shape. A windowed
class (`Dev`, `PercentRank`, `Bbw`, `Mode`,
`Range`, `Cog`, `Ultimate`, `Vortex`, `Aroon`, `Choppiness`,
`Efficiency`, `SourceStoch`) returns `NaN` while the bad bar sits inside its window and
heals when it leaves; the running sums (`AccDist`, `Wad`, `Pvt`) stay
`NaN` until `reset()`; the per-bar readings (`Wvad`, `Iii`) are `NaN` on
that bar alone; the classes built on `Ema` (`Dema`, `Tema`, `Zlema`,
`Trix`, and `Kcw` through `Keltner`) follow the `Ema` rule: a bad bar
before the seed restarts the seed, a bad bar after it makes the value
`NaN` for good. `Dmi` keeps TradingView's own rule for `ta.dmi` instead:
`ta.rma` skips a na bar with its state untouched, `ta.tr` and the two
changes are na on that bar and on the next one, so each smoother is fed
only when its own input is a value, `fixnan` holds `plusDi` and
`minusDi` meanwhile, `adx` keeps smoothing the DX of the held values,
and two bars after the hole every stream continues from the state it
had (the library's `Adx` goes `NaN` for good there). `Kama` re-seeds with the next `x` once its efficiency
window is clean again, the running extremes skip the bar, and `Nvi` and
`Pvi` never turn `NaN`: they carry their value across a bar whose close,
or previous close, is 0 or not a number. `SourceVwap` keeps the `Vwap`
rule (the current bucket is `NaN` until the next one starts), and
`HeikinAshi` reads `NaN` on the bad bar and restarts its chain on the
next finite one, seeding the open as the first bar does.

## Deviation, rank and width

Seven classes read one series. `Dev` and `PercentRank` say how far and
how high the newest value sits in its window; `Bbw` and `Kcw` are the
band widths as one number, with the bands in fields; `Range`, `Mode` and
`Cog` are the window's shape.

```typescript sample=fn-extra-indicators-widths
param("period", 20, { min: 2, max: 400, description: "Window for every class" });
param("mult", 2, { min: 0.5, max: 5, description: "Band multiplier" });
output("dev", line, lower, { color: "#2563eb", description: "Mean absolute deviation of the close" });
output("prank", line, lower, { color: "#7c3aed", description: "Percent rank of the close" });
output("bbw", line, lower, { color: "#16a34a", description: "Bollinger band width" });
output("bb_upper", line, lower, { color: "#15803d", description: "Upper Bollinger band" });
output("kcw", line, lower, { color: "#ea580c", description: "Keltner channel width" });
output("range", line, lower, { color: "#0891b2", description: "Highest minus lowest close" });
output("mode", line, lower, { color: "#4b5563", description: "Most frequent close" });
output("cog", line, lower, { color: "#be123c", description: "Centre of gravity" });

let dev = new Dev(20);
let prank = new PercentRank(20);
let bbw = new Bbw(20, 2.0);
let kcw = new Kcw(20, 2.0, 10);
let closeRange = new Range(20);
let mode = new Mode(20);
let cog = new Cog(20);

function onStart(): void {
  const period = i32(p_period());
  dev = new Dev(period);
  prank = new PercentRank(period);
  bbw = new Bbw(period, p_mult());
  kcw = new Kcw(period, p_mult(), period / 2);
  closeRange = new Range(period);
  mode = new Mode(period);
  cog = new Cog(period);
}

function onBar(): void {
  const close = bar.close();
  const devValue = dev.update(close);
  const prankValue = prank.update(close);
  const bbwValue = bbw.update(close);
  const kcwValue = kcw.update(close, bar.high(), bar.low(), close);
  const rangeValue = closeRange.update(close);
  const modeValue = mode.update(close);
  const cogValue = cog.update(close);
  if (isNaN(devValue)) return;
  out_dev(devValue);
  out_prank(prankValue);
  out_bbw(bbwValue);
  out_bb_upper(bbw.upper);
  out_kcw(kcwValue);
  out_range(rangeValue);
  out_mode(modeValue);
  out_cog(cogValue);
}
```

`PercentRank` counts the previous `period` values, never the current bar,
so its first value lands one bar after `Dev`'s: `onBar()` gates on `Dev`
and the first ready row draws a gap for the rank. `Kcw` reports its
`basis` once the EMA is seeded and the width once the ATR is warm too.

## Volume and money flow

`AccDist`, `Wad` and `Pvt` are running sums: the line so far, from the
first bar. `Nvi` and `Pvi` are indices that start at 1 and move only on
a bar whose volume fell (`Nvi`) or rose (`Pvi`). `Wvad` and `Iii` are
per-bar readings with no memory; for a running line feed one to the
library's `Cum`, as the sample does for `Wvad`.

```typescript sample=fn-extra-indicators-volume
input("open", ohlcv.open);
output("accdist", line, lower, { color: "#2563eb", description: "Accumulation/distribution line" });
output("wad", line, lower, { color: "#7c3aed", description: "Williams accumulation/distribution" });
output("wvad", line, lower, { color: "#16a34a", description: "Williams variable A/D of this bar" });
output("wvad_line", line, lower, { color: "#65a30d", description: "Running total of wvad" });
output("nvi", line, lower, { color: "#dc2626", description: "Negative volume index, from 1" });
output("pvi", line, lower, { color: "#ea580c", description: "Positive volume index, from 1" });
output("pvt", line, lower, { color: "#0891b2", description: "Price-volume trend" });
output("iii", line, lower, { color: "#4b5563", description: "Intraday intensity of this bar" });
output("running_max", line, lower, { color: "#15803d", description: "Highest close so far" });
output("running_min", line, lower, { color: "#be123c", description: "Lowest close so far" });

let accdist = new AccDist();
let wad = new Wad();
let wvad = new Wvad();
let wvadLine = new Cum();
let nvi = new Nvi();
let pvi = new Pvi();
let pvt = new Pvt();
let iii = new Iii();
let runningMax = new RunningMax();
let runningMin = new RunningMin();

function onStart(): void {
  accdist = new AccDist();
  wad = new Wad();
  wvad = new Wvad();
  wvadLine = new Cum();
  nvi = new Nvi();
  pvi = new Pvi();
  pvt = new Pvt();
  iii = new Iii();
  runningMax = new RunningMax();
  runningMin = new RunningMin();
}

function onBar(): void {
  const open = bar.open();
  const high = bar.high();
  const low = bar.low();
  const close = bar.close();
  const volume = bar.volume();
  const accdistValue = accdist.update(high, low, close, volume);
  const wadValue = wad.update(high, low, close);
  const wvadValue = wvad.update(open, high, low, close, volume);
  // A flat bar has no reading: add 0, Cum keeps a NaN.
  const wvadTotal = wvadLine.update(isNaN(wvadValue) ? 0.0 : wvadValue);
  const nviValue = nvi.update(close, volume);
  const pviValue = pvi.update(close, volume);
  const pvtValue = pvt.update(close, volume);
  const iiiValue = iii.update(high, low, close, volume);
  const maxValue = runningMax.update(close);
  const minValue = runningMin.update(close);
  if (isNaN(accdistValue)) return;
  out_accdist(accdistValue);
  out_wad(wadValue);
  out_wvad(wvadValue);
  out_wvad_line(wvadTotal);
  out_nvi(nviValue);
  out_pvi(pviValue);
  out_pvt(pvtValue);
  out_iii(iiiValue);
  out_running_max(maxValue);
  out_running_min(minValue);
}
```

A running line depends on where the history starts: the chart feeds the
module the bars it fetched, so the newest bar's value moves when that
window does. Read `AccDist`, `Wad`, `Pvt` and the two indices as shapes,
not as levels to alert on.

## Smoothers on the EMA

`Dema`, `Tema` and `Zlema` stack the library's `Ema`; `Trix` stacks three
of them over the logarithm of the price and reads the one-bar change;
`Efficiency` and `Kama` are Kaufman's pair, the ratio of net move to
total move and the average whose smoothing follows it.

```typescript sample=fn-extra-indicators-smoothers
param("period", 20, { min: 2, max: 400 });
param("fast", 2, { min: 1, max: 100, description: "Kama fast length" });
param("slow", 30, { min: 1, max: 400, description: "Kama slow length" });
output("dema", line, overlay, { color: "#2563eb", description: "Double EMA" });
output("tema", line, overlay, { color: "#7c3aed", description: "Triple EMA" });
output("zlema", line, overlay, { color: "#16a34a", description: "Zero-lag EMA" });
output("kama", line, overlay, { color: "#ea580c", width: 2, description: "Kaufman adaptive average" });
output("trix", line, lower, { color: "#dc2626", description: "TRIX" });
output("efficiency", line, lower, { color: "#0891b2", description: "Efficiency ratio, 0..1" });

let dema = new Dema(20);
let tema = new Tema(20);
let zlema = new Zlema(20);
let trix = new Trix(20);
let efficiency = new Efficiency(20);
let kama = new Kama(20, 2, 30);

function onStart(): void {
  const period = i32(p_period());
  dema = new Dema(period);
  tema = new Tema(period);
  zlema = new Zlema(period);
  trix = new Trix(period);
  efficiency = new Efficiency(period);
  kama = new Kama(period, i32(p_fast()), i32(p_slow()));
}

function onBar(): void {
  const close = bar.close();
  const demaValue = dema.update(close);
  const temaValue = tema.update(close);
  const zlemaValue = zlema.update(close);
  const trixValue = trix.update(close);
  const erValue = efficiency.update(close);
  const kamaValue = kama.update(close);
  if (isNaN(kamaValue)) return;
  out_dema(demaValue);
  out_tema(temaValue);
  out_zlema(zlemaValue);
  out_kama(kamaValue);
  out_trix(trixValue);
  out_efficiency(erValue);
}
```

The warm-ups differ: `Kama` has its first value at bar `period` while
`Tema(20)` waits until bar 57 and `Trix(20)` until bar 58. Gate
readiness on the line you plot as primary, not on the slowest class.

## Two oscillators

`Ultimate` blends buying pressure over three windows; `Vortex` reads the
two movement sums against the true range and exposes both lines.

```typescript sample=fn-extra-indicators-oscillators
param("period", 14, { min: 2, max: 400, description: "Vortex window" });
input("high", ohlcv.high);
output("ultimate", line, lower, { color: "#2563eb", description: "Ultimate oscillator (7, 14, 28)" });
output("vortex_plus", line, lower, { color: "#16a34a", description: "VI+" });
output("vortex_minus", line, lower, { color: "#dc2626", description: "VI-" });

let ultimate = new Ultimate(7, 14, 28);
let vortex = new Vortex(14);

function onStart(): void {
  ultimate = new Ultimate(7, 14, 28);
  vortex = new Vortex(i32(p_period()));
}

function onBar(): void {
  const high = bar.high();
  const low = bar.low();
  const close = bar.close();
  const ultimateValue = ultimate.update(high, low, close);
  const plus = vortex.update(high, low, close);
  const minus = vortex.minus;
  if (isNaN(plus)) return;
  out_ultimate(ultimateValue);
  out_vortex_plus(plus);
  out_vortex_minus(minus);
}
```

## A regime from Aroon and choppiness

`Aroon` says which way the recent extreme sits, `Choppiness` says whether
the window went anywhere at all. Together they make a three-way regime:
trending up, trending down, or ranging. The regime is a data-only output
that tints a shape through `color_by`
([Colors](colors-kit.md)).

```typescript sample=fn-extra-indicators-regime
param("period", 25, { min: 2, max: 400, description: "Aroon and choppiness window" });
param("chop_ceiling", 61.8, { min: 0, max: 100, description: "Choppiness above this reads as ranging" });
input("high", ohlcv.high);
output("aroon_up", line, lower, { color: "#16a34a", description: "Aroon up" });
output("aroon_down", line, lower, { color: "#dc2626", description: "Aroon down" });
output("chop", line, lower, { color: "#4b5563", description: "Choppiness index, 0..100" });
output("regime", none, overlay, { description: "0 ranging, 1 trending up, 2 trending down" });
output("regime_mark", shape, overlay, {
  color: "#94a3b8",
  color_by: "regime",
  colors: ["#94a3b8", "#16a34a", "#dc2626"],
  description: "The close, tinted by regime",
});

let aroon = new Aroon(25);
let chop = new Choppiness(25);
let chopCeiling: f64 = 61.8;

function onStart(): void {
  const period = i32(p_period());
  aroon = new Aroon(period);
  chop = new Choppiness(period);
  chopCeiling = p_chop_ceiling();
}

function onBar(): void {
  const high = bar.high();
  const low = bar.low();
  const close = bar.close();
  const osc = aroon.update(high, low);
  const chopValue = chop.update(high, low, close);
  if (isNaN(osc) || isNaN(chopValue)) return;
  let regime: f64 = 0.0;
  if (chopValue > chopCeiling) regime = 0.0;
  else regime = osc > 0.0 ? 1.0 : 2.0;
  out_aroon_up(aroon.up);
  out_aroon_down(aroon.down);
  out_chop(chopValue);
  out_regime(regime);
  out_regime_mark(close);
}
```

`Aroon` runs a `Highest` and a `Lowest` over `period + 1` bars and reads
their `bars` offsets, so `up` is 100 on the bar of a fresh high and falls
by `100 / period` on every bar the high ages; `Choppiness` is the log
ratio of the true-range sum to the window's range, near 100 when the
bars overlap and near 0 when they march in one direction. `onBar()`
returns before writing until both are warm: `Aroon` needs `period + 1`
bars, `Choppiness` needs `period`.

## Pine's spellings of four library classes

The library's `Adx`, `Stoch` and `Vwap` are the engine's forms: one
length, the close as the source, a named bar price. Four classes
here carry the Pine spellings a ported script writes. `Dmi(diLength,
adxSmoothing)` is `ta.dmi` with its two lengths (`+DI` and `-DI`
smoothed over `diLength`, ADX over `adxSmoothing`, both with the
library's `Rma`); with equal lengths it is `Adx` to the last bit of the
arithmetic, and across a missing bar it keeps TradingView's state rules
(nothing restarts, nothing is poisoned). `SourceStoch` is
`ta.stoch(source, high, low, length)` over any source, smoothed like
`Stoch`; fed the close it is `Stoch` on every window with a range (a
flat window follows TradingView: the previous raw %K when the source
sits on the window's value, `NaN` otherwise, where `Stoch` reads 0).
`SourceVwap` is `ta.vwap(source)` over any source with `Vwap`'s anchors;
fed `(high + low + close) / 3` it is `Vwap(anchor, "hlc3")`.
`HeikinAshi` is `ticker.heikinashi` as a class over the bars you feed it
(this chart's, or another market's candles); the recursion forgets its
start by half every bar, so after about 50 bars the values no longer
depend on where the loaded history begins.

```typescript
param("di_length", 14, { min: 1, max: 200 });
param("adx_smoothing", 7, { min: 1, max: 200 });
param("stoch_length", 14, { min: 1, max: 200 });
output("adx", line, lower, { color: "#111827", description: "ADX over its own smoothing" });
output("plus_di", line, lower, { color: "#16a34a", description: "+DI" });
output("minus_di", line, lower, { color: "#dc2626", description: "-DI" });
output("stoch_hl2", line, lower, { color: "#2563eb", description: "Stochastic of the bar midpoint" });
output("vwap_ohlc4", line, overlay, { color: "#f59e0b", description: "Session VWAP over ohlc4" });
output("ha_close", line, overlay, { color: "#7c3aed", description: "Heikin Ashi close" });

let dmi = new Dmi(14, 7);
let stoch = new SourceStoch(14);
const vwap = new SourceVwap("day");
const ha = new HeikinAshi();

function onStart(): void {
  dmi = new Dmi(i32(p_di_length()), i32(p_adx_smoothing()));
  stoch = new SourceStoch(i32(p_stoch_length()));
}

function onBar(): void {
  const o = bar.open();
  const h = bar.high();
  const l = bar.low();
  const c = bar.close();
  const adx = dmi.update(h, l, c);
  const k = stoch.update((h + l) / 2.0, h, l);
  const vwapValue = vwap.update((o + h + l + c) / 4.0, bar.volume(), bar.time() * 1000.0);
  const haClose = ha.update(o, h, l, c);
  if (isNaN(adx)) return;
  out_adx(adx);
  out_plus_di(dmi.plusDi);
  out_minus_di(dmi.minusDi);
  out_stoch_hl2(k);
  out_vwap_ohlc4(vwapValue);
  out_ha_close(haClose);
}
```

`Dmi` warms up last (`diLength + adxSmoothing - 1` bars), so the sample
gates on it. `SourceStoch(length)` is the raw %K; pass `smoothK` and
`periodD` for the `ta.sma` smoothing a Pine script applies afterwards.
`SourceVwap` takes the bar's open time in milliseconds like `Vwap`
([Volume and VWAP](volume-indicators.md#vwap)); `HeikinAshi` exposes the
four smoothed values as fields, read after `update()`.

## History and lists

`History` (Pine's `close[1]` as `history.ago(1)`), `List`, the `stats.*`
window functions, `HandleRing`, `roundTo`, `roundToTick` and `Random` have
their own page: [Stats, history and lists](stats-history-lists.md).

## From Pine

One to one: the class returns Pine's value on the same bars. The second
table holds the three that share Pine's formula with one difference. The
`array.*`, `x[n]`, `math.round` and `math.random` rows are on
[Stats, history and lists](stats-history-lists.md#from-pine).

| Pine | Extra indicators |
| --- | --- |
| `ta.dev(source, length)` | `new Dev(length)`, `.update(x)` |
| `ta.percentrank(source, length)` | `new PercentRank(length)`, `.update(x)` |
| `ta.bbw(source, length, mult)` | `new Bbw(length, mult)`, `.update(x)` |
| `ta.accdist` | `new AccDist()`, `.update(high, low, close, volume)` |
| `ta.wad` | `new Wad()`, `.update(high, low, close)` |
| `ta.wvad` | `new Wvad()`, `.update(open, high, low, close, volume)` |
| `ta.nvi` | `new Nvi()`, `.update(close, volume)` |
| `ta.pvi` | `new Pvi()`, `.update(close, volume)` |
| `ta.max(source)` | `new RunningMax()`, `.update(x)` |
| `ta.min(source)` | `new RunningMin()`, `.update(x)` |
| `ta.mode(source, length)` | `new Mode(length)`, `.update(x)` |
| `ta.range(source, length)` | `new Range(length)`, `.update(x)` |
| `ta.cog(source, length)` | `new Cog(length)`, `.update(x)` |
| `ta.iii` | `new Iii()`, `.update(high, low, close, volume)` |

| Pine | Extra indicators | The difference |
| --- | --- | --- |
| `ta.stoch(source, high, low, length)` with a `na` source, high or low | `new SourceStoch(length)`, `.update(source, high, low)` | TradingView carries the previous %K over a bar whose input is `na`; here the class reads `NaN` while that bar sits in its windows (the library's windowed rule) and heals when it leaves |
| `ta.pvt` | `new Pvt()`, `.update(close, volume)` | the same line from the second bar on; the first bar, with no previous close, reads 0 here |
| `ta.kcw(source, length, mult, true)` | `new Kcw(length, mult, length)`, `.update(x, high, low, close)` | the range average is the library's Wilder `Atr`; Pine's is an EMA of the true range, so the widths track each other without being equal |
