---
title: "Trend and volatility"
description: "Trend tools read direction and strength, volatility tools read range: the directional movement system, the Ichimoku cloud, the parabolic stop-and-reverse, the…"
order: 44
section: "functions"
---

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

# 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](oscillators.md#macd) page.
The full catalog is on the [TA library](ta-library.md) 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.

```typescript
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](series-functions.md)) 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](extra-indicators.md)), 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).

```typescript
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.minusDi
```

## Ichimoku

`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 `NaN` lead-in. A `NaN` high or low inside the window is
  skipped, but the current bar's own `NaN` poisons the value, and every
  output that comes out `NaN` is reported as `0`.
- **The displacement is inside the math.** `senkouA` on bar `i` is
  `(tenkan + kijun) / 2` as it stood `displacement` bars earlier, and
  `senkouB` is the 52-bar midpoint from `displacement` bars earlier; on
  the first `displacement` bars 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 no
  `displacement_bars`. The projection past the newest loaded bar is not
  emitted, exactly as in the engine.
- **`chikou` is the current close.** The engine reads the close of bar
  `i + displacement`, a future bar, and only falls back to the current
  close on the last `displacement` bars of the series; a class that sees
  one bar at a time cannot read ahead, so the field is the current close.
  Declaring `displacement_bars: -26` on 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.

```typescript
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 shifted
```

The 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](#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.

```typescript
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](../presentation/styling.md)).

## 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.

```typescript
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](moving-averages.md)
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](../presentation/styling.md)). 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](../presentation/cards-frames-panels.md));
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(period)` | `.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.

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

```typescript sample=fn-trend-kit
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.

```typescript sample=fn-special-trio
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.

```typescript sample=fn-trend-bands
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 `tenkan` over `kijun` cross that agrees with the cloud is
  the classic entry.
- **Supertrend.** The flip of `direction` is the signal; the line itself is
  the trailing stop for the position the regime implies.
