Extra indicators
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() 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.
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 | ( | ( | mean absolute deviation of the last period values from their Sma: sum( | bar period - 1 | |
Percent | ( | ( | 100 * count(, the current bar excluded from the count | bar period | |
Bbw | ( | ( | basis, upper, lower | ( over Bb(; Na when the basis is 0 | bar period - 1 |
Kcw | ( | ( | basis, upper, lower | ( over Keltner(; Na when the basis is 0 | bar max( |
Acc | ( | ( | running sum of (, the term 0 when high == low | bar 0 | |
Wad | ( | ( | running sum of: close - min( when the close rose, close - max( when it fell, else 0; bar 0 returns 0 | bar 0 | |
Wvad | ( | ( | ( for this bar, never a running sum; Na when high == low | bar 0 | |
Nvi | ( | ( | starts at 1; when volume < prev: nvi = prev + (, else prev; a close of 0 or Na, on this bar or the previous one, keeps prev | bar 0 | |
Pvi | ( | ( | the same with volume > prev | bar 0 | |
Pvt | ( | ( | running sum of (; bar 0 returns 0 | bar 0 | |
Running | ( | ( | the largest finite x seen so far (a non-finite x is skipped) | the first finite bar | |
Running | ( | ( | the smallest finite x seen so far (a non-finite x is skipped) | the first finite bar | |
Mode | ( | ( | the most frequent value of the window (exact equality); ties go to the smallest | bar period - 1 | |
Range | ( | ( | Highest - Lowest of the last period values | bar period - 1 | |
Cog | ( | ( | -( for i = 0 (the newest) to period - 1; Na when the sum is 0 | bar period - 1 | |
Iii | ( | ( | ( for this bar; Na when the denominator is 0 | bar 0 | |
Dema | ( | ( | 2 * e1 - e2, e1 = Ema( of x, e2 = Ema( of e1 | bar 2 * period - 2 | |
Tema | ( | ( | 3 * e1 - 3 * e2 + e3 | bar 3 * period - 3 | |
Zlema | ( | ( | Ema( of x + (, lag = floor( | bar lag + period - 1 | |
Trix | ( | ( | 10000 * the one-bar change of Ema(; Na when x <= 0 | bar 3 * period - 2 | |
Ultimate | ( | ( | BP = close - min(, TR = max(, avg_, result 100 * ( | bar p3 | |
Vortex | ( | ( | plus, minus | VM+ = abs(, VM- = abs(; plus = Sum(, minus = Sum( over period; returns plus | bar period |
Aroon | ( | ( | up, down, osc | up = 100 * (, down likewise with Lowest, osc = up - down; returns osc | bar period |
Choppiness | ( | ( | 100 * log10(; Na when the range is 0 | bar period - 1 | |
Efficiency | ( | ( | abs(; Na when the sum is 0 | bar period | |
Kama | ( | ( | sc = ( with er = Efficiency(; kama = prev + sc * (, seeded with x on the first bar er exists | bar period | |
Dmi | ( | ( | adx, plus, minus | Pine's ta.: +DI = 100 * Rma(, -DI likewise, DX = abs( (over 1 when the sum is 0), adx = 100 * Rma(; 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 | plus, minus at bar di; adx at bar di |
Source | ( | ( | k, d | Pine's ta.: raw %K is 100 * ( over period, k = Sma( of it, d = Sma( of k; a flat window follows TradingView (the previous raw %K when the source sits on the window's value, Na 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 period, d at bar period |
Source | ( | ( | Pine's ta.: the library's Vwap sums, sum( from the anchor ("", "day", "week", "month", "quarter", "year" or a bucket width in milliseconds; ts is bar., read only with an anchor), over any source; fed ( it is Vwap( | bar 0 | |
Heikin | ( | ( | open, high, low, close | Pine's ticker.: close = (, open = ( (the bar's own ( on the first bar), high = max(, low = min(; 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.
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.
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.
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.
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).
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.
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); 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.
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.
| Pine | Extra indicators |
|---|---|
ta.dev(source, length) | new Dev(length), .update(x) |
ta. | new Percent, .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. | 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. with a na source, high or low | new Source, .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 |