Trend and volatility
Trend tools read direction and strength, volatility tools read range: the directional movement system, the Ichimoku cloud, the parabolic stop-and-reverse, the ATR trailing stop, the band pair, and the volatility primitives under them.
Every one on this page ships as a class in
./sdk/ta (Adx, Ichimoku, Psar, Supertrend, Tr, Atr, Stdev,
Variance), and the bands are outputs plus a range() declaration. MACD
is an oscillator and lives on the Oscillators page.
The full catalog is on the TA library page.
| System | What it reads |
|---|---|
Directional movement (Adx) | trend strength as adx, with plusDi and minusDi; above 25 is a strong trend, below 20 often a range |
Ichimoku cloud (Ichimoku) | five components; the two leading spans come out of the class already shifted, the lagging span is drawn back by declaration |
Parabolic SAR (Psar) | a dot that trails price and jumps to the other side when the trend flips |
Supertrend (Supertrend) | an ATR-banded trailing stop with a direction the chart colors by |
Adx
new Adx(period = 14), .update(high, low, close) returns the ADX line;
after each update the fields adx, plusDi, and minusDi hold the three
streams. The class follows the engine's own arithmetic rather than a
textbook Wilder average: the true
range and the two directional movements are first summed over the first
period bars (a plain sum, not a mean), then each running sum is smoothed
as s = s - s / period + x; plusDi and minusDi are the smoothed
movements over the smoothed true range times 100 (0 when that range is
exactly 0); DX is the normalized DI difference, 0 when both DI lines
are 0; and adx seeds on the plain average of the first period DX
values before smoothing as (adx * (period - 1) + dx) / period. So the DI
lines appear at bar period and adx at bar 2 * period - 1. Bar 0 has
no previous bar and produces nothing; a non-finite bar before the seed
clears the partial sums and restarts the run, and a non-finite bar after
the seed leaves all three outputs NaN for the rest of the series.
let adx = new Adx(14);
function onStart(): void { adx = new Adx(i32(p_adx_period())); }Read adx.plusDi > adx.minusDi for direction and adx.adx rising for
conviction; a Cross object over the two DI lines
(Series functions) turns the crossover into a
signal.
Pine's ta.dmi(diLength, adxSmoothing) takes two lengths: +DI and
-DI over diLength, ADX over adxSmoothing. Adx smooths all three
over one period, so when the two differ reach for Dmi in
./sdk/ta-plus (Extra indicators), which keeps
Pine's form (Wilder's Rma on both) and the same three fields; with
equal lengths the two classes agree on a series without a hole (Dmi
skips a missing bar the way TradingView's ta.dmi does, where Adx
reads NaN for good).
let dmi = new Dmi(14, 14);
function onStart(): void { dmi = new Dmi(i32(p_di_length()), i32(p_adx_smoothing())); }
// after dmi.update(high, low, close): dmi.adx, dmi.plusDi, dmi.minusDiIchimoku
new Ichimoku(conversionPeriod = 9, basePeriod = 26, laggingSpanPeriod = 52, displacement = 26), .update(high, low, close) returns tenkan;
fields tenkan, kijun, senkouA, senkouB, chikou. Each line is the
midpoint of the highest high and lowest low over its period. The engine
conventions the class keeps:
- Partial windows, no warm-up. Bar 0 already has a value from its own
bar; there is no
NaNlead-in. ANaNhigh or low inside the window is skipped, but the current bar's ownNaNpoisons the value, and every output that comes outNaNis reported as0. - The displacement is inside the math.
senkouAon bariis(tenkan + kijun) / 2as it stooddisplacementbars earlier, andsenkouBis the 52-bar midpoint fromdisplacementbars earlier; on the firstdisplacementbars both fall back to the current bar's values. The two fields are therefore already the cloud that belongs on the current bar, so write them to plain outputs with nodisplacement_bars. The projection past the newest loaded bar is not emitted, exactly as in the engine. chikouis the current close. The engine reads the close of bari + displacement, a future bar, and only falls back to the current close on the lastdisplacementbars of the series; a class that sees one bar at a time cannot read ahead, so the field is the current close. Declaringdisplacement_bars: -26on its output makes the chart draw that close 26 bars back, which is the lagging span as the engine's chart shows it. That shift is drawing only: the value stays on the bar that computed it.
const cloud = new Ichimoku(9, 26, 52, 26);
// In onBar(): one update per bar, then read the five lines by name.
cloud.update(bar.high(), bar.low(), bar.close());
const bullish = cloud.senkouA >= cloud.senkouB; // the cloud on this bar, already shiftedThe cloud itself is two boxes: box("cloud_up", { top: senkouA, bottom: senkouB, when: aAbove }) draws, for each bar, a one-bar slice between the
two spans the class reports for that bar, and a second box gated the other
way draws the bearish slices in the other color. The declarations are
compiled in Cloud, stop, and dots below.
Psar
new Psar(start = 0.02, increment = 0.02, maxValue = 0.2), .update(high, low, close) returns the SAR price for the bar, which jumps to the other
side of price when the trend flips; there is no direction field, so read
the side as close > sar (or the SAR against the low), or track the flips
with a Cross object over price and the SAR. The acceleration factor
starts at start, grows by increment on every new extreme, and caps at
maxValue; the SAR never crosses the two prior bars' lows in an up trend
or highs in a down trend, and when price trades through it the trend
flips and the SAR jumps to the last extreme. Engine conventions: bar 0 is
NaN; bar 1 decides the opening trend from close[1] >= close[0] and
returns low[0] for an up trend or high[0] for a down one; the close is
only read on those two bars; a non-finite high or low (or close on bars 0
and 1) makes the SAR NaN for that bar and every bar after it, because
the engine recomputes the whole series and stops at the first bad bar.
Lower acceleration (0.01 to 0.02) gives smoother stops with fewer
reversals; 0.05 and up reacts faster and flips more in ranges.
let psar = new Psar(0.02, 0.02, 0.2);
function onBar(): void {
const close = bar.close();
const sar = psar.update(bar.high(), bar.low(), close);
if (isNaN(sar)) return;
const side = close > sar ? 1.0 : -1.0;
out_psar(sar);
out_side(side);
}Draw the SAR as a scatter output so it reads as dots, and color the dots
by side through a color_by ladder (Styling).
Supertrend
new Supertrend(factor, atrPeriod) (no defaults; 3 and 10 are the
usual values), .update(high, low, close) returns the stop line and fills
the fields line and direction (1 for an up trend, the line sits
below price; -1 for a down trend, the line sits above). The bands sit
factor ATRs either side of the bar midpoint (high + low) / 2; the
upper band ratchets down (a lower basic band, or the previous close above
the old band, replaces it) and the lower band ratchets up symmetrically;
the direction flips to -1 when the close drops below the lower band in
an up trend and to 1 when it rises above the upper band in a down
trend; the line is the lower band in an up trend and the upper band in a
down one. The ATR inside is the same Wilder smoothing as Atr, so the
first finite bar is atrPeriod - 1, where the direction starts as 1
when the close is at or above the midpoint. While the ATR is NaN both
fields are NaN and the band state is left untouched, so a gap neither
resets nor advances the bands; a non-finite high, low, or close on a bar
with a finite ATR gives NaN for that bar alone. Because a shape cannot
read a bool, the direction is a number, ready to be written to a
data-only output and used as a color_by index.
let st = new Supertrend(3.0, 10);
function onStart(): void {
st = new Supertrend(p_factor(), i32(p_atr_period()));
}
function onBar(): void {
st.update(bar.high(), bar.low(), bar.close());
if (isNaN(st.line)) return;
out_st_line(st.line);
out_st_dir(st.direction > 0.0 ? 1.0 : 0.0);
}Bands as outputs
Bb and Keltner (both described on the Moving averages
page) expose basis, upper, and lower fields; each becomes an output.
A wrun indicator declares the band between them.
range(upper, lower, options) names the two edge outputs (both must
be drawn) and the chart draws the band: both edge lines at edge_width
and edge_line_style with a tinted interior in color, a colors +
color_by ladder to tint it per bar, or a vertical gradient
(Styling). A box on every bar between the
two edge outputs, with from and to left at 0, is the other way to
shade (Cards, frames and panels);
the slices tile into a channel, and either edge may stay data-only.
Volatility primitives
| Class | Construct | Per bar | Returns |
|---|---|---|---|
Tr | new Tr() | .update(high, low, close) | the true range of the current bar: the largest of high - low, abs(high - prevClose), abs(low - prevClose), taken pairwise left to right; on bar 0 just high - low |
Atr | new Atr(period = 14) | .update(high, low, close) | the Wilder-smoothed true range: the seed is the plain average of the first period true ranges (first value at bar period - 1), then (prev * (period - 1) + tr) / period |
Stdev | new Stdev(period) | .update(x) | the rolling population standard deviation (divide by period, not period - 1) |
Variance | new Variance( | .update(x) | the rolling population variance, the same window arithmetic as Stdev without the square root |
All four ship in ./sdk/ta;
they are the building blocks the band and stop classes use internally,
exposed so custom volatility logic composes the same way. Their NaN
rules differ, and the differences are the engine's: Tr returns NaN on
a non-finite high or low and on the bar after a non-finite close; Atr
restarts its seed when a non-finite true range arrives before the seed
completes, and stays NaN for good after one arrives later (the engine
never reseeds); Stdev and Variance are strict windows, NaN until
period bars exist and whenever any value in the window is not finite,
healing as soon as the bad bar leaves.
const tr = new Tr();
let atr = new Atr(14);
let stdev = new Stdev(20);
let variance = new Variance(20);
function onStart(): void {
atr = new Atr(i32(p_atr_period()));
stdev = new Stdev(i32(p_period()));
variance = new Variance(i32(p_period()));
}
function onBar(): void {
const close = bar.close();
const trValue = tr.update(bar.high(), bar.low(), close);
const atrValue = atr.update(bar.high(), bar.low(), close);
const stdevValue = stdev.update(close);
const varianceValue = variance.update(close);
if (isNaN(atrValue)) return;
out_tr(trValue);
out_atr(atrValue);
out_stdev(stdevValue);
out_variance(varianceValue);
}Stdev is strict: a NaN sample anywhere in its window makes the result
NaN until the sample leaves the window; guard the input with isNaN
when a sparse source feeds it.
Putting them together
The directional system, the SAR, the Supertrend stop, and the volatility
primitives in one module. The stop line is colored by its regime through
a color_by ladder over a data-only st_dir output, the SAR draws as
dots, and the ADX trio and the four volatility lines share a lower pane.
param("adx_period", 14, { min: 1, max: 200 });
param("factor", 3, { min: 0.5, max: 10, description: "Supertrend ATR multiplier" });
param("atr_period", 10, { min: 1, max: 200, description: "ATR window for Supertrend and the atr line" });
output("st_line", line, overlay, { width: 2, color_by: "st_dir", colors: ["#dc2626", "#16a34a"], description: "Supertrend stop, red in a short regime, green in a long one" });
output("st_dir", none, overlay, { description: "0 short, 1 long: the palette index for st_line" });
output("psar", scatter, overlay, { color: "#9333ea", description: "Parabolic SAR dots" });
output("adx", line, lower, { color: "#111827", width: 2, description: "Average directional index" });
output("di_plus", line, lower, { color: "#2563eb", width: 1, description: "+DI" });
output("di_minus", line, lower, { color: "#dc2626", width: 1, description: "-DI" });
output("tr", line, lower, { color: "#94a3b8", width: 1, description: "True range" });
output("atr", line, lower, { color: "#f97316", width: 2, description: "Average true range" });
output("stdev", line, lower, { color: "#0891b2", width: 1, description: "Rolling standard deviation of the close" });
output("variance", line, lower, { color: "#7c3aed", width: 1, description: "Rolling variance of the close" });
let adx = new Adx(14);
let psar = new Psar(0.02, 0.02, 0.2);
let st = new Supertrend(3.0, 10);
let tr = new Tr();
let atr = new Atr(10);
let stdev = new Stdev(10);
let variance = new Variance(10);
function onStart(): void {
adx = new Adx(i32(p_adx_period()));
psar = new Psar(0.02, 0.02, 0.2);
st = new Supertrend(p_factor(), i32(p_atr_period()));
tr = new Tr();
atr = new Atr(i32(p_atr_period()));
stdev = new Stdev(i32(p_atr_period()));
variance = new Variance(i32(p_atr_period()));
}
function onBar(): void {
const close = bar.close();
const high = bar.high();
const low = bar.low();
adx.update(high, low, close);
const psarValue = psar.update(high, low, close);
st.update(high, low, close);
const trValue = tr.update(high, low, close);
const atrValue = atr.update(high, low, close);
const stdevValue = stdev.update(close);
const varianceValue = variance.update(close);
out_st_line(st.line);
out_st_dir(st.direction > 0.0 ? 1.0 : 0.0);
out_psar(psarValue);
out_adx(adx.adx);
out_di_plus(adx.plusDi);
out_di_minus(adx.minusDi);
out_tr(trValue);
out_atr(atrValue);
out_stdev(stdevValue);
out_variance(varianceValue);
}Cloud, stop, and dots
Ichimoku with its two-color cloud and the lagging span drawn back, the Supertrend stop colored by regime, and the SAR as dots, in one overlay.
param("factor", 3, { min: 0.5, max: 10, description: "Supertrend ATR multiplier" });
param("atr_period", 10, { min: 1, max: 200, description: "Supertrend ATR window" });
output("tenkan", line, overlay, { color: "#0891b2", width: 1, description: "Conversion line, 9-bar midpoint" });
output("kijun", line, overlay, { color: "#be123c", width: 1, description: "Base line, 26-bar midpoint" });
// The spans are already shifted inside the class: the value on a bar is the cloud for that bar.
const senkouA = output("senkou_a", line, overlay, { color: "#0f766e", width: 1, description: "Leading span A, the class shifts it 26 bars ahead" });
const senkouB = output("senkou_b", line, overlay, { color: "#b45309", width: 1, description: "Leading span B, the class shifts it 26 bars ahead" });
output("chikou", line, overlay, { color: "#64748b", width: 1, displacement_bars: -26, description: "Lagging span, the close drawn 26 bars back" });
const aAbove = output("a_above", none, overlay, { description: "1 where span A is above span B: the bullish cloud gate" });
const bAbove = output("b_above", none, overlay, { description: "1 where span B is above span A: the bearish cloud gate" });
// The cloud: one slice per bar between the two spans, tinted by which span is on top.
box("cloud_up", { top: senkouA, bottom: senkouB, when: aAbove, color: "#16a34a", opacity: 0.12, borderWidth: 0 });
box("cloud_down", { top: senkouB, bottom: senkouA, when: bAbove, color: "#dc2626", opacity: 0.12, borderWidth: 0 });
output("st_line", line, overlay, { width: 2, color_by: "st_dir", colors: ["#dc2626", "#16a34a"], description: "Supertrend stop, colored by regime" });
output("st_dir", none, overlay, { description: "0 short, 1 long" });
output("psar", scatter, overlay, { color: "#9333ea", description: "Parabolic SAR" });
let cloud = new Ichimoku(9, 26, 52, 26);
let st = new Supertrend(3.0, 10);
let psar = new Psar(0.02, 0.02, 0.2);
function onStart(): void {
cloud = new Ichimoku(9, 26, 52, 26);
st = new Supertrend(p_factor(), i32(p_atr_period()));
psar = new Psar(0.02, 0.02, 0.2);
}
function onBar(): void {
const close = bar.close();
const high = bar.high();
const low = bar.low();
cloud.update(high, low, close);
st.update(high, low, close);
const psarValue = psar.update(high, low, close);
out_tenkan(cloud.tenkan);
out_kijun(cloud.kijun);
out_senkou_a(cloud.senkouA);
out_senkou_b(cloud.senkouB);
out_chikou(cloud.chikou);
out_a_above(cloud.senkouA >= cloud.senkouB ? 1.0 : 0.0);
out_b_above(cloud.senkouB > cloud.senkouA ? 1.0 : 0.0);
out_st_line(st.line);
out_st_dir(st.direction > 0.0 ? 1.0 : 0.0);
out_psar(psarValue);
}Bands: outputs, a range, and a shaded box
The Bollinger pair as three outputs, a range() declaration recording the
band in the sheet, and a box on every bar shading the same pair on the
chart, gated so the shade only shows while the basis is rising.
param("period", 20, { min: 2, max: 400 });
param("mult", 2, { min: 0.5, max: 5, description: "Standard-deviation multiples" });
const upper = output("upper_band", line, overlay, { color: "#64748b", width: 1, description: "Basis plus mult deviations" });
output("basis", line, overlay, { color: "#2563eb", width: 1, description: "20-bar simple average" });
const lowerBand = output("lower_band", line, overlay, { color: "#64748b", width: 1, description: "Basis minus mult deviations" });
const rising = output("rising", none, overlay, { description: "1 while the basis rises: the shade gate" });
// The sheet-level band: honored by hosts that draw ranges, ignored by the chart lane today.
range("upper_band", "lower_band", { color: "#2563eb", edge_width: 1, edge_line_style: "dotted" });
// The chart-drawn band: one slice per bar between the same two outputs, tiling into a channel.
box("band_shade", { top: upper, bottom: lowerBand, when: rising, color: "#2563eb", opacity: 0.12, borderWidth: 0 });
let sma = new Sma(20);
let stdev = new Stdev(20);
let mult: f64 = 2.0;
let mid: f64 = NaN;
let prevMid: f64 = NaN;
function onStart(): void {
sma = new Sma(i32(p_period()));
stdev = new Stdev(i32(p_period()));
mult = p_mult();
}
function onBar(): void {
const close = bar.close();
prevMid = mid;
mid = sma.update(close);
const sd = stdev.update(close);
if (isNaN(mid) || isNaN(sd)) return;
out_upper_band(mid + mult * sd);
out_basis(mid);
out_lower_band(mid - mult * sd);
out_rising(!isNaN(prevMid) && mid > prevMid ? 1.0 : 0.0);
}Reading them
- Ichimoku. Price above the cloud (both spans) is an uptrend, below it
a downtrend; a
tenkanoverkijuncross that agrees with the cloud is the classic entry. - Supertrend. The flip of
directionis the signal; the line itself is the trailing stop for the position the regime implies.