---
title: "Moving averages"
description: "These classes smooth price into a trend line or wrap it in a band. Every one of them ships in every workspace as part of src/sdk/ta.ts: the whole kScript…"
order: 32
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 in every workspace as part of `src/sdk/ta.ts`: the whole
kScript (legacy) roster (`sma`, `ema`, `wma`, `hma`, `alma`, `swma`,
`vwma`, `rma`, `linreg`, `bb`, `keltner`, `donchian`) is a class with the
same name in title case, 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()`. Import the class you need from `./sdk/ta`,
construct it in `init()`, and call `update()` once per bar in `state()`.
Every class mirrors the kScript engine's arithmetic and its edge rules, so
a port draws the same line bar for bar ([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
on every host, so an abstaining `state()` (`return 0`) and a `NaN` written
to one output 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; call it from your `reset()` |

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, which is
the engine's rule too.

## 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
import { Sma } from "./sdk/ta";

let sma = new Sma(20);
export function init(): 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
import { Ema } from "./sdk/ta";

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
import { Wma } from "./sdk/ta";

let wma = new Wma(20);
export function init(): 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
import { Hma } from "./sdk/ta";

let hma = new Hma(21);
export function init(): 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
import { Alma } from "./sdk/ta";

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`; the
constructor defaults are the kScript defaults.

### 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
import { Swma } from "./sdk/ta";

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 a volume input beside the price input
(`input("volume", ohlcv.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
import { Vwma } from "./sdk/ta";

let vwma = new Vwma(20);
export function state(): i32 {
  const value = vwma.update(in_close(), in_volume());
  return isNaN(value) ? 0 : 1;
}
```

### 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 the kScript engine runs and the one `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
import { Rma } from "./sdk/ta";

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
import { Linreg } from "./sdk/ta";

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 kScript band helper returned three named streams. An 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 `box` on every bar ([Drawing primitives](drawing-primitives.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; kScript's usual call is
`new Bb(20, 2.0)`.

```typescript
import { Bb } from "./sdk/ta";

let bb = new Bb(20, 2.0);
export function finalize(): void {
  out_bb_upper(bb.upper);
  out_bb_basis(bb.basis);
  out_bb_lower(bb.lower);
  emitRow();
}
```

### 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 indicators](trend-indicators.md) 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
import { Keltner } from "./sdk/ta";

let kc = new Keltner(20, 1.5, 10);
export function state(): i32 {
  const close = in_close();
  kc.update(close, in_high(), in_low(), close);
  return 1;
}
```

### 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; the kScript builtin returns only
the midline). 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.

```typescript
import { Donchian } from "./sdk/ta";

let dc = new Donchian(20);
export function state(): i32 {
  dc.update(in_high(), in_low());
  return 1;
}
```

## 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 comes
from `./sdk/ta` in one import line. Each class is constructed in `init()`
from a param, updated once in `state()`, and its value written in
`finalize()`.

```typescript
import { input, line, ohlcv, output, overlay, param } from "./sdk/declare";
import { in_close, in_high, in_low, in_volume } from "./gen/inputs";
import {
  emitRow,
  out_alma,
  out_bb_basis,
  out_bb_lower,
  out_bb_upper,
  out_dc_basis,
  out_dc_lower,
  out_dc_upper,
  out_ema,
  out_hma,
  out_kc_basis,
  out_kc_lower,
  out_kc_upper,
  out_linreg,
  out_rma,
  out_sma,
  out_swma,
  out_vwma,
  out_wma,
} from "./gen/outputs";
import { p_atr_period, p_hull_period, p_kc_mult, p_mult, p_period } from "./gen/params";
import { Alma, Bb, Donchian, Ema, Hma, Keltner, Linreg, Rma, Sma, Swma, Vwma, Wma } from "./sdk/ta";

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" });
input("close", ohlcv.close);
input("high", ohlcv.high);
input("low", ohlcv.low);
input("volume", ohlcv.volume);
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);
let smaValue: f64 = NaN;
let emaValue: f64 = NaN;
let hmaValue: f64 = NaN;
let wmaValue: f64 = NaN;
let almaValue: f64 = NaN;
let swmaValue: f64 = NaN;
let vwmaValue: f64 = NaN;
let rmaValue: f64 = NaN;
let linregValue: f64 = NaN;

export function init(): 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);
}

export function state(): i32 {
  const close = in_close();
  const high = in_high();
  const low = in_low();
  smaValue = sma.update(close);
  emaValue = ema.update(close);
  hmaValue = hma.update(close);
  wmaValue = wma.update(close);
  almaValue = alma.update(close);
  swmaValue = swma.update(close);
  vwmaValue = vwma.update(close, in_volume());
  rmaValue = rma.update(close);
  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 the row is ready as soon as it has computed;
  // the slower lines simply write NaN until their own windows fill.
  return 1;
}

export function finalize(): void {
  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);
  emitRow();
}

export function reset(): void {
  sma.reset();
  ema.reset();
  hma.reset();
  wma.reset();
  alma.reset();
  swma.reset();
  vwma.reset();
  rma.reset();
  linreg.reset();
  bb.reset();
  kc.reset();
  dc.reset();
  smaValue = NaN;
  emaValue = NaN;
  hmaValue = NaN;
  wmaValue = NaN;
  almaValue = NaN;
  swmaValue = NaN;
  vwmaValue = NaN;
  rmaValue = NaN;
  linregValue = NaN;
}
```

The whole module is one chart overlay with eighteen legend entries; on
your machine every one of them is a metric id (`wrun/@you/<name>/hma`,
`.../bb_upper`, and so on) that an alert or a screen can read.

## 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, still exists as a
metric, and the chart shows only the two bands. All three begin together
once the 20-bar window completes, because one `Bb` feeds them.

```typescript
import { input, line, none, ohlcv, output, overlay, param } from "./sdk/declare";
import { in_close } from "./gen/inputs";
import { emitRow, out_bb_basis, out_bb_lower, out_bb_upper, out_sma20 } from "./gen/outputs";
import { p_period } from "./gen/params";
import { Bb, Sma } from "./sdk/ta";

param("period", 20, { min: 2, max: 400 });
input("close", ohlcv.close);
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 metric 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);
let value: f64 = NaN;

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

export function state(): i32 {
  const close = in_close();
  value = sma.update(close);
  bb.update(close);
  // Abstain the whole row while the window is filling: no legend value, no metric row, nothing drawn.
  return isNaN(value) ? 0 : 1;
}

export function finalize(): void {
  out_sma20(value);
  out_bb_upper(bb.upper);
  out_bb_basis(bb.basis);
  out_bb_lower(bb.lower);
  emitRow();
}

export function reset(): void {
  sma.reset();
  bb.reset();
  value = NaN;
}
```

## 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
import { input, line, lower, ohlcv, output, param } from "./sdk/declare";
import { in_close } from "./gen/inputs";
import { emitRow, out_alma_check, out_swma_check } from "./gen/outputs";
import { p_length } from "./gen/params";
import { Alma, Swma } from "./sdk/ta";

param("length", 10, { min: 2, max: 200 });
input("close", ohlcv.close);
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;
let almaCheck: f64 = NaN;
let swmaCheck: f64 = NaN;

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

export function state(): i32 {
  const close = in_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;
  almaCheck = Math.abs(a - b) < 0.000001 ? b : NaN;
  swmaCheck = Math.abs(s - manual) < 0.000001 ? s : NaN;
  return isNaN(almaCheck) && isNaN(swmaCheck) ? 0 : 1;
}

export function finalize(): void {
  out_alma_check(almaCheck);
  out_swma_check(swmaCheck);
  emitRow();
}

export function reset(): void {
  almaDefault.reset();
  almaExplicit.reset();
  swma.reset();
  x0 = NaN;
  x1 = NaN;
  x2 = NaN;
  x3 = NaN;
  almaCheck = NaN;
  swmaCheck = NaN;
}
```

## What changed from kScript

- A builtin call (`sma(source=close, period=20)`) became an object you
  own: construct it in `init()`, keep it in a module-level `let`, feed it
  in `state()`. 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 became three fields read
  after `update()` and three outputs. Nothing indexes into a tuple.
- `vwma(source=trade)` reading volume off an OHLC series became an explicit
  `input("volume", ohlcv.volume)` and a two-argument `update(price,
  volume)`: an Indicator names every field it reads.
- Warm-up stays `NaN`, and `NaN` propagates through arithmetic exactly as
  `na` did. `isNaN(x)` is the test; there is no `nz` builtin, but a
  two-line helper is on the [Series functions](series-functions.md) page.
- The numbers are the engine's numbers: every class here matches the
  kScript engine bar for bar, so a port is a rename, not a re-tune. The
  full catalog is on the [TA library](ta-library.md) page.
