---
title: "Oscillators"
description: "Oscillators measure momentum and overbought or oversold pressure. Every oscillator ships with the editor as a stateful class in ./sdk/ta: Rsi, Wpr, Cmo, Tsi…"
order: 43
section: "functions"
---

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

# Oscillators

Oscillators measure momentum and overbought or oversold pressure. Every
oscillator ships with the editor as a stateful class in `./sdk/ta`:
`Rsi`, `Wpr`, `Cmo`, `Tsi`, `Macd`, `Stoch`, `Stochastic`, `Cci`, `Mfi`,
`Mom`, and `Roc` (the full catalog is on the [TA library](ta-library.md)
page). Name the ones you use; there is nothing to import. A multi-output
oscillator exposes its streams as fields after `update()` (`macd.signal`,
`stoch.k`) and each one goes to its own output.

Every oscillator needs a warm-up window. Until enough bars have loaded to
fill its longest period the value is `NaN` and nothing draws. `Rsi(14)` is
`NaN` for its seed window and `Macd` warms up over the slow average plus
the signal period. Leading bars are blank, then the line begins. The
compiled modules below also fold two classes that live elsewhere: `Adx`,
which takes longest because it smooths directional movement twice, on
[Trend and volatility](trend-indicators.md#adx), and `Obv` on
[Volume and VWAP](volume-indicators.md#obv).

## Reference

Every class allocates in its constructor, never in `update()`, and
`reset()` restores the just-constructed state. A `period` below 1 is
clamped to 1. Construct in `onStart()` from a param (params are `f64`,
periods are `i32`, so `new Rsi(i32(p_rsi_period()))`).

### Rsi

`new Rsi(period)`, `.update(x)`: the relative strength index, bounded
0..100. Wilder smoothing (the gain and loss averages of the first `period`
one-bar changes seed it, then `avg = (avg * (period - 1) + gain) / period`),
first value at bar `period`. The ratio step is `100 - 100 / (1 + gain /
loss)`, and a zero average loss returns `100`: a flat window reads 100,
never 50; all-loss reads 0. A non-finite input before the seed restarts the
seed count; after the seed it makes the value `NaN` for good.

```typescript
let rsi = new Rsi(14);
function onStart(): void { rsi = new Rsi(i32(p_rsi_period())); }
```

### Wpr

`new Wpr(length = 14)`, `.update(high, low, close)`: Williams %R over a
high/low window, `-100 * (highest high - close) / (highest high - lowest
low)`, bounded -100..0. `NaN` for the first `length - 1` bars and whenever
any high or low in the window (or the close) is non-finite; a flat window
(highest equals lowest) returns `0`.

```typescript
let wpr = new Wpr(14);
function onStart(): void { wpr = new Wpr(i32(p_length())); }
```

### Cmo

`new Cmo(length = 9)`, `.update(x)`: the Chande momentum oscillator
compares summed gains and losses over the last `length` one-bar changes,
`100 * (up - down) / (up + down)`. `NaN` until bar `length` (the first bar
has no previous value), `0` when `up + down == 0` (a flat window), and
`NaN` when any value in the window, or the bar before it, is non-finite.

```typescript
let cmo = new Cmo(14);
function onStart(): void { cmo = new Cmo(i32(p_length())); }
```

### Tsi

`new Tsi(short = 13, long = 25)`, `.update(x)`: the true strength index
double-smooths one-bar momentum and its absolute value with an EMA of
`long` then an EMA of `short`, and returns their ratio times 100. The
engine's parameter order is `short` first, then `long`. Each EMA stage
seeds on the mean of its first `period` finite inputs, so the first value
lands at bar `long + short - 1`; the ratio is `NaN` when either stage is
not finite or the denominator is `0`.

```typescript
let tsi = new Tsi(13, 25);
function onStart(): void { tsi = new Tsi(i32(p_short()), i32(p_long())); }
```

### Macd

`new Macd(fastPeriod = 12, slowPeriod = 26, signalPeriod = 9)`,
`.update(x)`: returns the MACD line and fills the fields `macd`, `signal`,
and `hist`. The MACD line is the fast EMA minus the slow EMA, each leg
seeded on the mean of its first `period` finite inputs (first value at
bar `slowPeriod - 1`), the signal is an EMA of that line seeded on its first
`signalPeriod` finite values (first value at bar `slowPeriod + signalPeriod
- 2`), and `hist` is `macd - signal` when both are finite. A non-finite
input after a seed poisons that leg to `NaN`. Draw the histogram as a
`histogram` output around zero, or test a signal-line cross with
`Cross.update(macd.macd, macd.signal)` ([Series functions](series-functions.md)).

```typescript
let macd = new Macd(12, 26, 9);
function onStart(): void { macd = new Macd(i32(p_fast()), i32(p_slow()), i32(p_signal())); }
// after macd.update(close): macd.macd, macd.signal, macd.hist
```

### Stoch and Stochastic

`new Stoch(periodK, smoothK, periodD)`, `.update(high, low, close)`: the
stochastic oscillator. Raw %K is `100 * (close - lowest low) / (highest high
- lowest low)` over `periodK` bars (`0` on a flat window, `NaN` while the
window is short or holds a non-finite value), `k` is the strict simple
average of the last `smoothK` raw values, and `d` is the same average of the
last `periodD` values of `k`. `update()` returns `k`; read `k` and `d` as
fields. First `k` at bar `periodK + smoothK - 2`, first `d` at bar `periodK
+ smoothK + periodD - 3`.

Two spellings with different edge rules ship. `new
Stochastic(kPeriod = 14, kSmoothing = 3, dPeriod = 3)` is the other
one: bars before `kPeriod - 1` report `k = 0`
and `d = 0` (not `NaN`), a flat window reads `50`, `k` is the raw value
itself until the smoothing window fills, `d` equals `k` until its own
window fills, and a `NaN` `k` or `d` is reported as `50`. Reach for `Stoch`
unless you want those edge rules.

```typescript
let stoch = new Stoch(14, 3, 3);
function onStart(): void { stoch = new Stoch(i32(p_period_k()), 3, 3); }
// after stoch.update(high, low, close): stoch.k, stoch.d
```

Both read the close as the source. Pine's `ta.stoch(source, high, low,
length)` takes any source: `SourceStoch` in `./sdk/ta-plus`
([Extra indicators](extra-indicators.md)) moves the source into
`update(source, high, low)` and keeps `Stoch`'s smoothing and fields;
fed the close it is `Stoch` on every window with a range (on a flat
window `Stoch` reads 0 where `SourceStoch` follows TradingView: the
previous raw %K when the source sits on the window's value, `NaN`
otherwise).

```typescript
let stochHl2 = new SourceStoch(14, 3, 3);
function onStart(): void { stochHl2 = new SourceStoch(i32(p_period_k()), 3, 3); }
// in onBar(): stochHl2.update((bar.high() + bar.low()) / 2.0, bar.high(), bar.low()); stochHl2.k, stochHl2.d
```

### Cci

`new Cci(period = 20, constant = 0.015)`, `.update(high, low, close)`:
the commodity channel index over the typical price `(high + low + close) /
3`, `(tp - sma) / (constant * meanDev)` where `sma` is the window mean of
the typical price and `meanDev` the mean absolute deviation around it.
Readings beyond +100 and -100 mark momentum extremes. `NaN` for the first
`period - 1` bars; `0` when the mean deviation is `0`.

```typescript
let cci = new Cci(20, 0.015);
function onStart(): void { cci = new Cci(i32(p_period()), 0.015); }
```

### Mfi

`new Mfi(period = 14)`, `.update(high, low, close, volume)`: the money
flow index, a volume-weighted RSI bounded 0..100. Each bar's raw flow is
`typical price * volume`, added to the positive sum when the typical price
rose against the previous bar, to the negative sum when it fell, and to
neither when equal; the result is `100 - 100 / (1 + positive / negative)`,
and a zero negative sum returns `100`. `NaN` for the first `period` bars;
a non-finite bar fails both comparisons and adds nothing, as in the
engine. It needs volume, so pass `bar.volume()` beside the prices. Above
80 reads overbought and below 20 oversold; price at a new high while the
index above 80 is not is a classic exhaustion read.

```typescript
let mfi = new Mfi(14);
function onStart(): void { mfi = new Mfi(i32(p_period())); }
```

### Mom and Roc

`new Roc(n)`, `.update(x)`: rate of change in percent, `((x - x[n]) /
x[n]) * 100`, `NaN` for the first `n` bars and when the lagged value is
`0`. `new Mom(n)`, `.update(x)`: momentum, the raw difference `x - x[n]`,
`NaN` for the first `n` bars and when either value is non-finite. `Change`
is the same math under another name with a one-bar default lag; it lives
with the series helpers on
[Series functions](series-functions.md#trend-extremes-and-counting).

```typescript
let mom = new Mom(10);
let roc = new Roc(10);
function onStart(): void { mom = new Mom(i32(p_lag())); roc = new Roc(i32(p_lag())); }
```

## Putting them together

One module wiring every oscillator above into a lower pane, the
multi-output ones (`Macd`, `Stoch`, `Adx`) written stream by stream. Every
class is used by name with nothing to import. `onBar()` returns before
writing until the slowest line (`Adx`) is warm so every output starts
together; write each value as it comes instead if you want the fast lines
to appear first, since a `NaN` draws nothing
([Execution model](../core-concepts/execution-model.md)).

```typescript sample=fn-oscillators
param("rsi_period", 14, { min: 2, max: 200 });
param("fast", 12, { min: 1, max: 200, description: "MACD fast EMA" });
param("slow", 26, { min: 2, max: 400, description: "MACD slow EMA" });
param("signal", 9, { min: 1, max: 200, description: "MACD signal EMA" });
param("adx_period", 14, { min: 1, max: 200 });
output("rsi", line, lower, { color: "#7c3aed", width: 2, description: "Relative strength index" });
output("wpr", line, lower, { color: "#2563eb", width: 1, description: "Williams %R" });
output("cmo", line, lower, { color: "#16a34a", width: 1, description: "Chande momentum oscillator" });
output("tsi", line, lower, { color: "#9333ea", width: 1, description: "True strength index" });
output("macd", line, lower, { color: "#1d4ed8", width: 2, description: "MACD line" });
output("signal", line, lower, { color: "#ea580c", width: 2, description: "MACD signal" });
output("hist", histogram, lower, { color: "#15803d", description: "MACD histogram" });
output("stoch_k", line, lower, { color: "#0e7490", width: 2, description: "Stochastic %K" });
output("stoch_d", line, lower, { color: "#be123c", width: 2, description: "Stochastic %D" });
output("cci", line, lower, { color: "#4b5563", width: 1, description: "Commodity channel index" });
output("mfi", line, lower, { color: "#16a34a", width: 2, description: "Money flow index" });
output("mom", line, lower, { color: "#9333ea", width: 1, description: "Momentum over 10 bars" });
output("roc", line, lower, { color: "#0891b2", width: 1, unit: "%", description: "Rate of change over 10 bars" });
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("obv", line, lower, { color: "#0f766e", width: 1, description: "On-balance volume" });

let rsi = new Rsi(14);
let wpr = new Wpr(14);
let cmo = new Cmo(14);
let tsi = new Tsi(13, 25);
let macd = new Macd(12, 26, 9);
let stoch = new Stoch(14, 3, 3);
let cci = new Cci(20, 0.015);
let mfi = new Mfi(14);
let mom = new Mom(10);
let roc = new Roc(10);
let adx = new Adx(14);
let obv = new Obv();

function onStart(): void {
  rsi = new Rsi(i32(p_rsi_period()));
  wpr = new Wpr(14);
  cmo = new Cmo(14);
  tsi = new Tsi(13, 25);
  macd = new Macd(i32(p_fast()), i32(p_slow()), i32(p_signal()));
  stoch = new Stoch(14, 3, 3);
  cci = new Cci(20, 0.015);
  mfi = new Mfi(14);
  mom = new Mom(10);
  roc = new Roc(10);
  adx = new Adx(i32(p_adx_period()));
  obv = new Obv();
}

function onBar(): void {
  const close = bar.close();
  const high = bar.high();
  const low = bar.low();
  const volume = bar.volume();
  const rsiValue = rsi.update(close);
  const wprValue = wpr.update(high, low, close);
  const cmoValue = cmo.update(close);
  const tsiValue = tsi.update(close);
  macd.update(close);
  stoch.update(high, low, close);
  const cciValue = cci.update(high, low, close);
  const mfiValue = mfi.update(high, low, close, volume);
  const momValue = mom.update(close);
  const rocValue = roc.update(close);
  adx.update(high, low, close);
  const obvValue = obv.update(close, volume);
  // ADX is the slowest line here; return before writing until it is warm so every output begins together.
  if (isNaN(adx.adx)) return;
  out_rsi(rsiValue);
  out_wpr(wprValue);
  out_cmo(cmoValue);
  out_tsi(tsiValue);
  out_macd(macd.macd);
  out_signal(macd.signal);
  out_hist(macd.hist);
  out_stoch_k(stoch.k);
  out_stoch_d(stoch.d);
  out_cci(cciValue);
  out_mfi(mfiValue);
  out_mom(momValue);
  out_roc(rocValue);
  out_adx(adx.adx);
  out_di_plus(adx.plusDi);
  out_di_minus(adx.minusDi);
  out_obv(obvValue);
}
```

## Edge behavior: Wpr, Cmo, Tsi

Two edges are worth seeing: %R is `NaN` until its window fills, and CMO
returns `0` on a flat series. One module shows both with the shipped
classes. `flat` is a series held at 100 on
every bar, so its `Cmo` changes are all zero and the class returns `0` once
its window is full; `wpr_warm` is `1` on the bars where the class has an
answer and `0` before, so the boundary is a step you can read off the pane.

```typescript sample=fn-wpr-cmo-tsi
param("length", 14, { min: 2, max: 200 });
output("wpr", line, lower, { color: "#2563eb", width: 2, description: "Williams %R" });
output("cmo", line, lower, { color: "#16a34a", width: 2, description: "Chande momentum oscillator" });
output("tsi", line, lower, { color: "#7c3aed", width: 2, description: "True strength index, short 13 long 25" });
output("wpr_warm", none, lower, { description: "1 once %R has a full window, 0 before" });
output("cmo_flat", line, lower, { color: "#ea580c", width: 1, description: "CMO of a flat series: 0 once its window fills" });

let wpr = new Wpr(14);
let cmo = new Cmo(14);
let cmoFlat = new Cmo(5);
let tsi = new Tsi(13, 25);

function onStart(): void {
  wpr = new Wpr(i32(p_length()));
  cmo = new Cmo(i32(p_length()));
  cmoFlat = new Cmo(5);
  tsi = new Tsi(13, 25);
}

function onBar(): void {
  const close = bar.close();
  const wprValue = wpr.update(bar.high(), bar.low(), close);
  const cmoValue = cmo.update(close);
  const cmoFlatValue = cmoFlat.update(100.0);
  const tsiValue = tsi.update(close);
  out_wpr(wprValue);
  out_cmo(cmoValue);
  out_tsi(tsiValue);
  out_wpr_warm(isNaN(wprValue) ? 0.0 : 1.0);
  out_cmo_flat(cmoFlatValue);
}
```

## Warm-up in practice

To see warm-up directly, draw a few oscillators and look at where each line
begins. The leading gap is the seed window: each line stays blank until its
longest period has enough bars, then turns finite. `Adx` starts last (bar
`2 * period - 1`), `Rsi` first (bar `period`), `Stoch` at bar `period + 1`
with its default smoothing of 3, and `Macd` once its slow EMA is seeded.
This module writes every output on every bar (a `NaN` draws nothing)
instead of returning early, so the pane shows each line switching on at
its own bar.

```typescript sample=fn-warmup-boundary
param("period", 14, { min: 2, max: 200 });
output("rsi", line, lower, { color: "#7c3aed", width: 2, description: "Finite from bar period on" });
output("macd", line, lower, { color: "#2563eb", width: 2, description: "Finite once the slow EMA is seeded" });
output("stoch_k", line, lower, { color: "#0891b2", width: 2, description: "Finite after the %K window plus smoothing" });
output("adx", line, lower, { color: "#111827", width: 2, description: "Finite after two smoothing windows" });

let rsi = new Rsi(14);
let macd = new Macd(12, 26, 9);
let stoch = new Stoch(14, 3, 3);
let adx = new Adx(14);

function onStart(): void {
  const period = i32(p_period());
  rsi = new Rsi(period);
  macd = new Macd(12, 26, 9);
  stoch = new Stoch(period, 3, 3);
  adx = new Adx(period);
}

function onBar(): void {
  const close = bar.close();
  const rsiValue = rsi.update(close);
  macd.update(close);
  stoch.update(bar.high(), bar.low(), close);
  adx.update(bar.high(), bar.low(), close);
  // Every bar has a row; each output carries NaN until its own class is warm.
  out_rsi(rsiValue);
  out_macd(macd.macd);
  out_stoch_k(stoch.k);
  out_adx(adx.adx);
}
```

## Points to remember

- Every oscillator takes the fields it needs as arguments, each one read
  from the bar (`bar.high()`, `bar.low()`, `bar.close()`, `bar.volume()`);
  reading a field is what puts it on the sheet.
- `macd.hist`, `stoch.d`, and the ADX trio are fields on the class and
  one output each.
- A MACD divergence (price at a new high while `macd.macd` is not) is a
  weakening trend; the `hist` output shrinking toward zero says the same
  thing earlier.
- Every class here is checked bit-exact against the reference run on the
  [TA library](ta-library.md) page, edge rules included (`Rsi` at 100 on
  a flat window, `Stochastic` at 50, `Wpr` and `Cmo` at 0).
