---
title: "Moving averages"
description: "These classes smooth price into a trend line or wrap it in a band. Every one of them ships with the editor in the ./sdk/ta module (Sma, Ema, Wma, Hma, Alma…"
order: 42
section: "functions"
---

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

# Moving averages

These classes smooth price into a trend line or wrap it in a band. Every
one of them ships with the editor in the `./sdk/ta` module (`Sma`,
`Ema`, `Wma`, `Hma`, `Alma`, `Swma`, `Vwma`, `Rma`, `Linreg`, `Bb`,
`Keltner`, `Donchian`), each one built the same way: allocate in the
constructor, fold one bar per `update()`, return `NaN` until the window is
full, restore with `reset()`. Name the class you need (nothing to import),
construct it in `onStart()`, and call `update()` once per bar in `onBar()`.
Every class has fixed arithmetic and edge rules, named in its section
([TA library](ta-library.md) lists the full catalog and the proof).

Every windowed average is `NaN` until its window fills. An `Sma(20)` draws
nothing for its first 19 bars, then begins once the 20th has loaded; the
Hull average warms up a little longer because of its internal weighted
sub-windows. Donchian is the exception: it reads the rolling extreme of
whatever has loaded from the very first bar. `NaN` is the warm-up signal,
so a `NaN` written to one output and an early `return` from `onBar()`
that leaves every output unwritten are the two honest ways to say "not
yet" ([Execution model](../core-concepts/execution-model.md)).

The shape every class shares:

| Member | Meaning |
| --- | --- |
| `new X(period)` | allocates the window once; a `period` below 1 is clamped to 1 |
| `.update(x): f64` | folds one bar, returns the current value, `NaN` until warm |
| `.reset(): void` | back to the freshly constructed state; nothing on the chart calls it for you |

A non-finite input (a `NaN` or an infinity) inside a window makes the
windowed averages return `NaN` for as long as that bar sits in the window;
the two running averages (`Ema`, `Rma`) never recover from one.

## Single-line averages

### Sma

`new Sma(period)`, `.update(x)`: the unweighted mean of the last `period`
values, summed oldest to newest and divided by the period. Warm-up is
`period` bars (first value at bar `period - 1`).

```typescript
let sma = new Sma(20);
function onStart(): void { sma = new Sma(i32(p_period())); }
```

Read the period through its accessor and cast it: params are `f64`, class
periods are `i32`, and AssemblyScript refuses the implicit conversion.

### Ema

`new Ema(period)`, `.update(x)`: the exponential average. Weights recent
bars more heavily, so it turns faster than `Sma`. The first `period`
finite values seed it with their simple mean, then `x * alpha + prev * (1
- alpha)` with `alpha = 2 / (period + 1)` takes over, so it is `NaN` for
`period - 1` bars. A non-finite input before the seed completes restarts
the seed count; one after the seed sets the value to `NaN` for good (the
engine never reseeds).

```typescript
let ema = new Ema(20);
```

### Wma

`new Wma(period)`, `.update(x)`: the linearly weighted average. The newest
bar carries weight `period`, the oldest weight `1`, so it sits between
`Sma` and `Ema` in responsiveness. The weighted sum walks oldest to newest
and is divided by `period * (period + 1) / 2`; `NaN` until `period` bars
have been seen and whenever any value in the window is not finite.

```typescript
let wma = new Wma(20);
function onStart(): void { wma = new Wma(i32(p_period())); }
```

### Hma

`new Hma(period)`, `.update(x)`: the Hull average, three `Wma` windows
composed as `WMA(2 * WMA(round(n / 2)) - WMA(n), round(sqrt(n)))`. Very
low lag and smooth. The half and square-root lengths are rounded (never
below 1), the inner difference is `NaN` until both inner windows are warm,
and the outer window then needs `round(sqrt(n))` finite values, so the
first value lands at bar `n - 1 + round(sqrt(n)) - 1`.

```typescript
let hma = new Hma(21);
function onStart(): void { hma = new Hma(i32(p_hull_period())); }
```

### Alma

`new Alma(length, offset = 0.85, sigma = 6)`, `.update(x)`: the Arnaud
Legoux average, a Gaussian-weighted window. `offset` places the peak of
the bell at `offset * (length - 1)` from the oldest bar (near the newest
bar by default), `sigma` sets its width as `length / sigma`. The weights
are computed once in the constructor with the engine's arithmetic, so
`update()` is a single weighted pass, walked oldest to newest. `NaN` until
`length` bars have been seen and whenever any value in the window is not
finite.

```typescript
let alma = new Alma(20);              // offset 0.85, sigma 6
let sharp = new Alma(20, 0.9, 4.0);   // peak closer to the newest bar, narrower bell
```

Omitting `offset` and `sigma` is the same as passing `0.85` and `6`.

### Swma

`new Swma()`, `.update(x)`: the symmetrically weighted average, the fixed
four-tap smoother `(x[3] + 2 * x[2] + 2 * x[1] + x[0]) / 6` with `x[0]`
the newest bar. `NaN` on the first three bars (first value at bar 3) and
whenever any of the four values is not finite; no period to choose.

```typescript
let swma = new Swma();
```

### Vwma

`new Vwma(period)`, `.update(price, volume)`: the volume-weighted average
takes two values per bar, `sum(price * volume) / sum(volume)` over the
window. Bars with more volume pull the average harder, so it tracks where
trading actually happened. It needs the bar's volume beside the price
(`bar.volume()`). `NaN` until `period` bars have been
seen, whenever any price or volume in the window is not finite, and when
the volume sum is exactly `0`.

```typescript
let vwma = new Vwma(20);
function onBar(): void { out_vwma(vwma.update(bar.close(), bar.volume())); }
```

### Rma

`new Rma(period)`, `.update(x)`: the running (Wilder) average, the
smoothing inside RSI and ATR. The first `period` finite values seed it
with their simple mean, then `rma = (rma * (period - 1) + x) / period`.
Smoother and slower than `Ema` for the same period. This is the same
Wilder accumulator `Rsi` and `Atr` use internally, so an RSI built by
hand over `Rma` and the shipped `Rsi` agree. A non-finite input before the seed completes restarts the seed
count; one after the seed sets the value to `NaN` for good.

```typescript
let rma = new Rma(14);
```

### Linreg

`new Linreg(period, offset = 0)`, `.update(x)`: linear regression fits a
least-squares line over the last `period` bars (x positions `0` oldest to
`period - 1` newest) and returns its value at the newest bar (`offset`
`0`) or `offset` bars back along the fitted line: the trend's fitted price
rather than an average. `offset` is an `f64` truncated to an integer. `NaN`
until `period` bars have been seen and whenever any value in the window
is not finite; a period of `1` returns the value itself.

```typescript
let linreg = new Linreg(20);        // value at the newest bar
let lagged = new Linreg(20, 2.0);   // the fitted line two bars back
```

## Band helpers

A band is three named streams. A wrun indicator has no
tuple outputs: the class exposes `upper`, `basis`, and `lower` as fields
after each `update()`, and you write each one to its own output
([Named streams](../core-concepts/named-streams.md)). Shading between the
edges is a `range()` band over the two drawn edges
([Styling](../presentation/styling.md)) or a `box` on every bar
([Cards, frames and panels](../presentation/cards-frames-panels.md)).

### Bb

`new Bb(period, mult)`, `.update(x)`: Bollinger bands. `basis` is the
window mean (the same arithmetic as `Sma`), `upper` and `lower` sit `mult`
population standard deviations away (the same arithmetic as `Stdev`, the
textbook Bollinger form). `update()` returns the basis and fills the three
fields. All three are `NaN` until `period` bars exist and whenever any bar
of the window is not finite; the window heals as soon as the bad bar
leaves it. Both arguments are required; the usual call is
`new Bb(20, 2.0)`.

```typescript
let bb = new Bb(20, 2.0);
function onBar(): void {
  bb.update(bar.close());
  out_bb_upper(bb.upper);
  out_bb_basis(bb.basis);
  out_bb_lower(bb.lower);
}
```

### Keltner

`new Keltner(period, mult, atrPeriod)`, `.update(x, high, low, close)`:
like Bollinger, but the width comes from the average true range instead
of the standard deviation, so it reacts to range rather than dispersion.
`basis` is an `Ema(period)` over `x` (usually the close), the width is an
`Atr(atrPeriod)` over the three prices (the Wilder-smoothed true range;
its class is on the [Trend and volatility](trend-indicators.md#volatility-primitives) page)
multiplied by `mult`. `update()` returns the basis and fills the three
fields. The basis is reported as soon as the `Ema` is seeded, even while
the ATR is still warming up; `upper` and `lower` are `NaN` unless both the
basis and the ATR are finite.

```typescript
let kc = new Keltner(20, 1.5, 10);
function onBar(): void {
  const close = bar.close();
  kc.update(close, bar.high(), bar.low(), close);
  out_kc_upper(kc.upper);
  out_kc_basis(kc.basis);
  out_kc_lower(kc.lower);
}
```

### Donchian

`new Donchian(period = 12)`, `.update(high, low)`: the highest high and
lowest low over `period` bars as `upper` and `lower`, with their midpoint
as `basis` (the value `update()` returns). Because it reads the rolling
extreme rather than averaging,
it is finite from the first loaded bar: the window is partial at the
start, and bar 0 returns `(high + low) / 2` of that bar alone. The current
bar's high and low always take part, even when `NaN` (so a `NaN` bar
propagates), while non-finite highs or lows of older bars are skipped.
The period is used as given: the clamp to 1 the other classes apply does
not run here.

```typescript
let dc = new Donchian(20);
function onBar(): void {
  dc.update(bar.high(), bar.low());
  out_dc_upper(dc.upper);
  out_dc_basis(dc.basis);
  out_dc_lower(dc.lower);
}
```

## Putting them together

Every average and band on one chart: the nine single-line averages on the
price pane, the three band helpers as nine more outputs. Every class is
used by name with nothing to import. Each class is constructed in
`onStart()` from a param, then updated once and its value written in
`onBar()`.

```typescript sample=fn-moving-averages
param("period", 20, { min: 2, max: 400, description: "Window for every average and band" });
param("hull_period", 21, { min: 4, max: 400, description: "Hull average window" });
param("mult", 2, { min: 0.5, max: 5, description: "Bollinger standard-deviation multiplier" });
param("kc_mult", 1.5, { min: 0.5, max: 5, description: "Keltner ATR multiplier" });
param("atr_period", 10, { min: 1, max: 200, description: "Keltner ATR window" });
output("sma", line, overlay, { color: "#2563eb", width: 2, description: "Simple moving average" });
output("ema", line, overlay, { color: "#dc2626", width: 2, description: "Exponential moving average" });
output("hma", line, overlay, { color: "#7c3aed", width: 2, description: "Hull moving average" });
output("wma", line, overlay, { color: "#ea580c", width: 2, description: "Weighted moving average" });
output("alma", line, overlay, { color: "#0891b2", width: 2, description: "Arnaud Legoux moving average" });
output("swma", line, overlay, { color: "#0f766e", width: 1, description: "Symmetrically weighted moving average" });
output("vwma", line, overlay, { color: "#b45309", width: 2, description: "Volume-weighted moving average" });
output("rma", line, overlay, { color: "#059669", width: 2, description: "Wilder running moving average" });
output("linreg", line, overlay, { color: "#4b5563", width: 2, description: "Least-squares regression value" });
output("bb_upper", line, overlay, { color: "#0f766e", width: 1, description: "Bollinger upper band" });
output("bb_basis", line, overlay, { color: "#64748b", width: 1, description: "Bollinger basis" });
output("bb_lower", line, overlay, { color: "#be123c", width: 1, description: "Bollinger lower band" });
output("kc_upper", line, overlay, { color: "#16a34a", width: 1, description: "Keltner upper channel" });
output("kc_basis", line, overlay, { color: "#94a3b8", width: 1, description: "Keltner basis" });
output("kc_lower", line, overlay, { color: "#dc2626", width: 1, description: "Keltner lower channel" });
output("dc_upper", line, overlay, { color: "#0ea5e9", width: 1, description: "Donchian upper band" });
output("dc_basis", line, overlay, { color: "#94a3b8", width: 1, description: "Donchian midpoint" });
output("dc_lower", line, overlay, { color: "#f97316", width: 1, description: "Donchian lower band" });

let sma = new Sma(20);
let ema = new Ema(20);
let hma = new Hma(21);
let wma = new Wma(20);
let alma = new Alma(20);
let swma = new Swma();
let vwma = new Vwma(20);
let rma = new Rma(20);
let linreg = new Linreg(20);
let bb = new Bb(20, 2.0);
let kc = new Keltner(20, 1.5, 10);
let dc = new Donchian(20);

function onStart(): void {
  const period = i32(p_period());
  sma = new Sma(period);
  ema = new Ema(period);
  hma = new Hma(i32(p_hull_period()));
  wma = new Wma(period);
  alma = new Alma(period);
  swma = new Swma();
  vwma = new Vwma(period);
  rma = new Rma(period);
  linreg = new Linreg(period, 0.0);
  bb = new Bb(period, p_mult());
  kc = new Keltner(period, p_kc_mult(), i32(p_atr_period()));
  dc = new Donchian(period);
}

function onBar(): void {
  const close = bar.close();
  const high = bar.high();
  const low = bar.low();
  const smaValue = sma.update(close);
  const emaValue = ema.update(close);
  const hmaValue = hma.update(close);
  const wmaValue = wma.update(close);
  const almaValue = alma.update(close);
  const swmaValue = swma.update(close);
  const vwmaValue = vwma.update(close, bar.volume());
  const rmaValue = rma.update(close);
  const linregValue = linreg.update(close);
  bb.update(close);
  kc.update(close, high, low, close);
  dc.update(high, low);
  // Donchian is finite from the first bar, so its band draws at once;
  // the slower lines simply write NaN until their own windows fill.
  out_sma(smaValue);
  out_ema(emaValue);
  out_hma(hmaValue);
  out_wma(wmaValue);
  out_alma(almaValue);
  out_swma(swmaValue);
  out_vwma(vwmaValue);
  out_rma(rmaValue);
  out_linreg(linregValue);
  out_bb_upper(bb.upper);
  out_bb_basis(bb.basis);
  out_bb_lower(bb.lower);
  out_kc_upper(kc.upper);
  out_kc_basis(kc.basis);
  out_kc_lower(kc.lower);
  out_dc_upper(dc.upper);
  out_dc_basis(dc.basis);
  out_dc_lower(dc.lower);
}
```

The whole module is one chart overlay with eighteen legend entries, and
once the indicator is published every one of them is an output an alert
can follow ([Alerts](alerts.md)).

## Warm-up and named streams

Two ideas worth seeing on their own: an average is blank until its window
fills, and a band's three levels are three independent outputs you can
draw or hide one at a time. The Bollinger edges below are drawn while the
basis stays data-only (`none`): it still computes and you can read it at
the Console prompt, but the chart shows only the two bands. All three
begin together once the 20-bar window completes, because one `Bb` feeds
them.

```typescript sample=fn-warmup-streams
param("period", 20, { min: 2, max: 400 });
output("sma20", line, overlay, { color: "#2563eb", width: 2, description: "NaN until the window is complete" });
output("bb_upper", line, overlay, { color: "#16a34a", width: 2, description: "Bollinger upper band" });
output("bb_basis", none, overlay, { description: "Computed, never drawn: a value without a line" });
output("bb_lower", line, overlay, { color: "#dc2626", width: 2, description: "Bollinger lower band" });

let sma = new Sma(20);
let bb = new Bb(20, 2.0);

function onStart(): void {
  sma = new Sma(i32(p_period()));
  bb = new Bb(i32(p_period()), 2.0);
}

function onBar(): void {
  const close = bar.close();
  const value = sma.update(close);
  bb.update(close);
  // Return before writing while the window is filling: no legend value, nothing drawn.
  if (isNaN(value)) return;
  out_sma20(value);
  out_bb_upper(bb.upper);
  out_bb_basis(bb.basis);
  out_bb_lower(bb.lower);
}
```

## Alma and Swma boundaries

`Alma`'s omitted `offset` and `sigma` default to `0.85` and `6`, so a
default construction and an explicit one agree to the last bit; `Swma`
uses the fixed four-tap weighting, so it agrees with the arithmetic
spelled out by hand over the last four closes. The module below computes
both forms and writes each line only where the class agrees with the
manual form, which is every bar once the windows are warm: a `NaN`
written on a disagreeing bar would leave a visible hole.

```typescript sample=fn-alma-swma
param("length", 10, { min: 2, max: 200 });
output("alma_check", line, lower, { color: "#2563eb", width: 2, description: "ALMA with the default offset 0.85 and sigma 6" });
output("swma_check", line, lower, { color: "#16a34a", width: 2, description: "SWMA against the four-tap arithmetic" });

let almaDefault = new Alma(10);
let almaExplicit = new Alma(10, 0.85, 6.0);
let swma = new Swma();
// The manual SWMA keeps the last four closes by hand: x0 is the newest.
let x0: f64 = NaN;
let x1: f64 = NaN;
let x2: f64 = NaN;
let x3: f64 = NaN;

function onStart(): void {
  almaDefault = new Alma(i32(p_length()));
  almaExplicit = new Alma(i32(p_length()), 0.85, 6.0);
  swma = new Swma();
}

function onBar(): void {
  const close = bar.close();
  const a = almaDefault.update(close);
  const b = almaExplicit.update(close);
  const s = swma.update(close);
  x3 = x2;
  x2 = x1;
  x1 = x0;
  x0 = close;
  const manual = (x3 + 2.0 * x2 + 2.0 * x1 + x0) / 6.0;
  const almaCheck = Math.abs(a - b) < 0.000001 ? b : NaN;
  const swmaCheck = Math.abs(s - manual) < 0.000001 ? s : NaN;
  out_alma_check(almaCheck);
  out_swma_check(swmaCheck);
}
```

## Points to remember

- An average is an object you own: construct it in `onStart()`, keep it
  in a module-level `let`, feed it in `onBar()`. There is no series to
  pass in; the class sees one value per bar, which is the whole
  [execution model](../core-concepts/execution-model.md).
- A band's `upper` / `basis` / `lower` streams are three fields read
  after `update()` and three outputs. Nothing indexes into a tuple.
- `Vwma` reads volume through `bar.volume()` and a two-argument
  `update(price, volume)`: a wrun indicator names every field it reads.
- Warm-up is `NaN`, and `NaN` propagates through arithmetic. `isNaN(x)`
  is the test; a two-line `nz` helper is on the
  [Series functions](series-functions.md) page.
- Every class here is checked bit-exact against the reference run
  described on the [TA library](ta-library.md) page, edge rules included.
