---
title: "TA library"
description: "Every workspace ships src/sdk/ta.ts: the whole kScript (legacy) TA catalog as stateful classes, one class per builtin, 52 in all. Each class folds one bar per…"
order: 37
section: "functions"
---

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

# TA library

Every workspace ships `src/sdk/ta.ts`: the whole kScript (legacy) TA
catalog as stateful classes, one class per builtin, 52 in all. Each class
folds one bar per `update()` call, keeps its own window, and mirrors the
engine's arithmetic, warm-up, and missing-value rules bar for bar, so a
port from kScript reads the same numbers it read before. This page is the
catalog: every class with its constructor, its `update()` arguments, its
fields, the builtin it replaces, and the one convention you need to know
about it. The function pages ([Moving averages](moving-averages.md),
[Oscillators](oscillators.md), [Trend indicators](trend-indicators.md),
[Special indicators](special-indicators.md),
[Volume indicators](volume-indicators.md),
[Series functions](series-functions.md),
[Utility functions](utility-functions.md)) go deeper on each group with
compiled examples.

## How every class works

- **Allocate in the constructor, never in `update()`**, so per-bar memory
  stays flat.
- **`update(...)` folds one bar and returns the current value**, `NaN`
  until the class is warm (a few classes report a partial window from bar
  0 instead; the tables say which).
- **Multi-output classes fill fields.** `update()` returns the primary
  line and the other lines sit in public fields you read after the call.
- **`reset()` restores the just-constructed state.** The host replays the
  forming bar through `reset()` on every tick.
- **Periods are `i32`, params are `f64`.** Construct in `init()` from a
  param (`new Sma(i32(p_period()))`), keep the object in a module-level
  `let`, update it once per bar in `state()`, and call `.reset()` on it
  from your `reset()`. A period below 1 is clamped to 1 (except
  `Donchian`, which uses the period as given, like the engine).

## The shipped classes

### Averages and smoothing

| Class | Construct | Per bar | Replaces | Convention |
| --- | --- | --- | --- | --- |
| `Sma` | `new Sma(period)` | `.update(x)` | `sma`, `mean` | plain window mean; `NaN` until `period` bars exist and whenever the window holds a non-finite value |
| `Ema` | `new Ema(period)` | `.update(x)` | `ema` | the mean of the first `period` finite values seeds it, then `x * alpha + prev * (1 - alpha)` with `alpha = 2 / (period + 1)`; a non-finite input after the seed makes it `NaN` for good |
| `Rma` | `new Rma(period)` | `.update(x)` | `rma` | Wilder smoothing, the accumulator inside `Rsi` and `Atr`: same seed as `Ema`, then `(prev * (period - 1) + x) / period` |
| `Wma` | `new Wma(period)` | `.update(x)` | `wma` | linear weights, the newest value weighs `period`, the oldest 1; strict window |
| `Hma` | `new Hma(period)` | `.update(x)` | `hma` | `wma(2 * wma(x, round(period / 2)) - wma(x, period), round(sqrt(period)))`; first value at bar `period - 1 + round(sqrt(period)) - 1` |
| `Vwma` | `new Vwma(period)` | `.update(x, volume)` | `vwma` | `sum(x * volume) / sum(volume)`; `NaN` when the volume sum is 0 |
| `Alma` | `new Alma(length, offset = 0.85, sigma = 6)` | `.update(x)` | `alma` | Gaussian weights centered at `offset * (length - 1)`, width `length / sigma`; strict window |
| `Swma` | `new Swma()` | `.update(x)` | `swma` | `(x[3] + 2 x[2] + 2 x[1] + x[0]) / 6` with `x[0]` the newest; first value at bar 3 |
| `Linreg` | `new Linreg(period, offset = 0)` | `.update(x)` | `linreg` | least-squares line through the window evaluated at `period - 1 - offset` (offset truncated to an integer); strict window |

### Statistics and series math

| Class | Construct | Per bar | Replaces | Convention |
| --- | --- | --- | --- | --- |
| `Sum` | `new Sum(period)` | `.update(x)` | `sum` | no warm-up: bar 0 already returns the partial window; `NaN` inputs are skipped |
| `Median` | `new Median(period)` | `.update(x)` | `median` | middle of the sorted window, the mean of the two middle values on an even period; strict window |
| `Percentile` | `new Percentile(period, pct)` | `.update(x)` | `percentile` | nearest rank: `rank = ceil(pct / 100 * period)`, result `sorted[max(0, rank - 1)]`; `pct` clamped to 0..100; strict window |
| `Variance` | `new Variance(period)` | `.update(x)` | `variance` | population variance (divide by `period`, not `period - 1`); strict window |
| `Stdev` | `new Stdev(period)` | `.update(x)` | `stdev`, `stddev` | population standard deviation, the square root of `Variance`; strict window |
| `Zscore` | `new Zscore(period)` | `.update(x)` | `zScore` | `(x - mean) / stdev` over the window; `NaN` bars inside the window are skipped for the sums, the variance still divides by `period`; `0` when the deviation is exactly 0 |
| `Correlation` | `new Correlation(period)` | `.update(a, b)` | `correlation` | Pearson correlation of two series; `NaN` when either window holds a non-finite value or the denominator is 0 |
| `Change` | `new Change(n = 1)` | `.update(x)` | `change` | `x - x[n]`; `NaN` for the first `n` bars |
| `Mom` | `new Mom(n)` | `.update(x)` | `mom` | the same math as `Change`; kScript exposes both names |
| `Roc` | `new Roc(period)` | `.update(x)` | `roc` | `(x - x[n]) / x[n] * 100`; `NaN` for the first `n` bars and when `x[n]` is 0 |
| `Cum` | `new Cum()` | `.update(x)` | `cum` | running sum from bar 0; a non-finite bar makes it `NaN` for good |
| `Fixnan` | `new Fixnan()` | `.update(x)` | `fixnan` | repeats the last finite value over a non-finite bar; `NaN` until the first finite value |

### Oscillators and momentum

| Class | Construct | Per bar | Replaces | Convention |
| --- | --- | --- | --- | --- |
| `Rsi` | `new Rsi(period)` | `.update(x)` | `rsi` | Wilder RSI; bar 0 feeds nothing, first value at bar `period`; a zero average loss returns 100, so a flat window is 100 |
| `Cmo` | `new Cmo(length)` | `.update(x)` | `cmo` | `100 * (up - down) / (up + down)` over the last `length` changes; first value at bar `length`; a zero total returns 0 |
| `Tsi` | `new Tsi(short = 13, long = 25)` | `.update(x)` | `tsi` | double EMA (long, then short) of momentum over the double EMA of its absolute value; the engine's parameter order is `(short, long)`; first value at bar `long + short - 1` |
| `Cci` | `new Cci(period = 20, constant = 0.015)` | `.update(high, low, close)` | `cci` | over typical price `(high + low + close) / 3`; first value at bar `period - 1`; 0 when the mean deviation is 0 |
| `Mfi` | `new Mfi(period = 14)` | `.update(high, low, close, volume)` | `mfi` | money flow over typical price times volume; first value at bar `period`; a zero negative flow returns 100 |
| `Wpr` | `new Wpr(length = 14)` | `.update(high, low, close)` | `wpr` | Williams %R; first value at bar `length - 1`; a flat window returns 0 |
| `Stoch` | `new Stoch(periodK, smoothK, periodD)` | `.update(high, low, close)` returns `k` | `stoch` | fields `k`, `d`; raw %K is 0 on a flat window; first `k` at bar `periodK + smoothK - 2`, first `d` at bar `periodK + smoothK + periodD - 3` |
| `Stochastic` | `new Stochastic(kPeriod = 14, kSmoothing = 3, dPeriod = 3)` | `.update(high, low, close)` returns `k` | `stochastic` | the older builtin with its own rules: `k = d = 0` before bar `kPeriod - 1` (never `NaN`), 50 on a flat window, a `NaN` `k` or `d` is reported as 50 |
| `Macd` | `new Macd(fast = 12, slow = 26, signal = 9)` | `.update(x)` returns `macd` | `macd` | fields `macd`, `signal`, `hist`; the line appears at bar `slow - 1`, the signal at bar `slow + signal - 2`; `hist = macd - signal` |
| `Obv` | `new Obv()` | `.update(close, volume)` | `obv` | on-balance volume; bar 0 returns 0; an unchanged close adds nothing |

### Ranges and bands

| Class | Construct | Per bar | Replaces | Convention |
| --- | --- | --- | --- | --- |
| `Tr` | `new Tr()` | `.update(high, low, close)` | `tr` | `max(high - low, abs(high - prevClose), abs(low - prevClose))`; bar 0 is plain `high - low` |
| `Atr` | `new Atr(period = 14)` | `.update(high, low, close)` | `atr` | `Rma` of `Tr`; first value at bar `period - 1`; a non-finite range after the seed makes it `NaN` for good |
| `Bb` | `new Bb(period, mult)` | `.update(x)` returns `basis` | `bb` | fields `basis`, `upper`, `lower`; `Sma` basis and population `Stdev` width; all three `NaN` while the window holds a non-finite value |
| `Keltner` | `new Keltner(period, mult, atrPeriod)` | `.update(x, high, low, close)` returns `basis` | `keltner` | fields `basis`, `upper`, `lower`; `Ema` basis plus `Atr` width; the basis is reported as soon as the EMA is seeded, the bands once the ATR is finite |
| `Donchian` | `new Donchian(period = 12)` | `.update(high, low)` returns `basis` | `donchian` | fields `basis` (the midline the builtin returns), `upper`, `lower`; no warm-up, the window is partial at the start; older `NaN` highs and lows are skipped, the current bar's `NaN` propagates |
| `Highest` | `new Highest(period = 12)` | `.update(x)` | `highest` | the window maximum; field `bars` is the `highestbars` offset (0 = this bar, negative = bars ago, the newest bar wins a tie); kScript reads the HIGH column by default, so pass the high |
| `Lowest` | `new Lowest(period = 12)` | `.update(x)` | `lowest` | the window minimum; field `bars` is the `lowestbars` offset; pass the low for the default column |
| `HighestBars` | `new HighestBars(period)` | `.update(x)` | `highestbars` | the offset as the primary value (0 or negative); field `value` is the matching high |
| `LowestBars` | `new LowestBars(period)` | `.update(x)` | `lowestbars` | the offset as the primary value; field `value` is the matching low |

### Trend systems

| Class | Construct | Per bar | Replaces | Convention |
| --- | --- | --- | --- | --- |
| `Adx` | `new Adx(period = 14)` | `.update(high, low, close)` returns `adx` | `adx` | fields `adx`, `plusDi`, `minusDi`; the seed is the plain sum of the first `period` true ranges and directional moves, then `s = s - s / period + x` (the engine's form, not an `Rma`); `+DI` and `-DI` appear at bar `period`, ADX at bar `2 * period - 1`; a non-finite bar after the seed leaves every output `NaN` for good |
| `Ichimoku` | `new Ichimoku(conversionPeriod = 9, basePeriod = 26, laggingSpanPeriod = 52, displacement = 26)` | `.update(high, low, close)` returns `tenkan` | `ichimoku` | fields `tenkan`, `kijun`, `senkouA`, `senkouB`, `chikou`; windows are partial from bar 0 (no `NaN` warm-up), the first `displacement` bars use the current bar's values instead of shifted ones, a `NaN` output is reported as 0; `chikou` is the current close (see the accuracy section) |
| `Psar` | `new Psar(start = 0.02, increment = 0.02, maxValue = 0.2)` | `.update(high, low, close)` | `psar` | the stop-and-reverse level; bar 0 is `NaN`, bar 1 picks the first trend from `close[1] >= close[0]`; a non-finite bar makes every later bar `NaN`, as the engine's full recompute does |
| `Supertrend` | `new Supertrend(factor, atrPeriod)` | `.update(high, low, close)` returns `line` | `supertrend` | fields `line`, `direction` (`1` up, the line sits below price; `-1` down); both `NaN` while the ATR is `NaN`, and the band state is left untouched across such a gap |
| `Vwap` | `new Vwap(anchor = "", price = "hlc3")` | `.update(open, high, low, close, volume, tsMs = NaN)` | `vwap` | `anchor` is `""` (one accumulation from bar 0), `"day"`, `"week"`, `"month"`, `"quarter"`, `"year"` (UTC calendar boundaries) or a numeric string bucket width in milliseconds; `tsMs` is the bar's open time in milliseconds since the epoch and is read only when an anchor is set (the `time` source hands seconds, multiply by 1000); `price` is `hlc3`, `hl2`, `ohlc4`, `hlcc4`, or `close`; a non-finite bar makes the current bucket `NaN` until the next one starts |

### Events and conditions

| Class | Construct | Per bar | Replaces | Convention |
| --- | --- | --- | --- | --- |
| `Rising` | `new Rising(period)` | `.update(x)` | `rising` | 1 when `x` is strictly above every one of the previous `period` values, else 0; `NaN` while the bar index is below `period` |
| `Falling` | `new Falling(period)` | `.update(x)` | `falling` | the mirror of `Rising` |
| `PivotHigh` | `new PivotHigh(leftbars, rightbars)` | `.update(high)` | `pivothigh` | the value of the bar `rightbars` back when it beats every value `leftbars` before and `rightbars` after it, reported only on the confirming bar, `NaN` otherwise; ties never count |
| `PivotLow` | `new PivotLow(leftbars, rightbars)` | `.update(low)` | `pivotlow` | the mirror of `PivotHigh` |
| `ValueWhen` | `new ValueWhen(occurrence)` | `.update(condition, x)` | `valuewhen` | `x` on the most recent bar where `condition` was true (`occurrence` 0), or the one before (`1`); a condition is true when it is finite and not 0; the current bar counts |
| `BarsSince` | `new BarsSince()` | `.update(condition)` | `barssince` | bars since the condition was last true, 0 on a true bar; `NaN` until the first true bar |
| `Cross` | `new Cross()` | `.update(a, b): i32` | `crossover`, `crossunder`, `cross` | `+1` when `a` crosses above `b`, `-1` below, `0` otherwise; the engine's previous-bar rule: crossover is `prevA < prevB && a >= b`, crossunder is `prevA > prevB && a <= b`; any `NaN` among the four values gives 0, and so does the first bar |

`Zscore` carries the engine's `zScore` builtin. `Cross` folds three
builtins into one return value: test `> 0`, `< 0`, or `!= 0`.

A composite over the shipped classes: a `Macd` read through its three
fields, a `Cross` gate over the line and its signal, and an `Rsi`, with a
hand-written sheet beside the source (the metadata-first shape; the
code-first shape declares the same params, one input, and four outputs at
the top of the file):

```json
{
  "id": "momentum-kit",
  "abi_version": "wrun-1",
  "warmup_bars": 1,
  "params": [
    { "name": "fast", "default": 12, "min": 1, "max": 200 },
    { "name": "slow", "default": 26, "min": 2, "max": 400 },
    { "name": "signal", "default": 9, "min": 1, "max": 200 },
    { "name": "rsi_len", "default": 14, "min": 2, "max": 200 }
  ],
  "inputSources": { "close": { "source": "ohlcv", "field": "close" } },
  "inputs": [{ "index": 0, "name": "close" }],
  "outputs": [
    { "index": 0, "name": "macd", "plot": "line", "panel": "lower" },
    { "index": 1, "name": "signal", "plot": "line", "panel": "lower" },
    { "index": 2, "name": "rsi", "plot": "line", "panel": "lower" },
    { "index": 3, "name": "crossed", "plot": "" }
  ]
}
```

```typescript
import { in_close } from "./gen/inputs";
import { emitRow, out_crossed, out_macd, out_rsi, out_signal } from "./gen/outputs";
import { p_fast, p_rsi_len, p_signal, p_slow } from "./gen/params";
import { Cross, Macd, Rsi } from "./sdk/ta";

let macd = new Macd(12, 26, 9);
let rsi = new Rsi(14);
let cross = new Cross();
let strength: f64 = NaN;
let crossed: f64 = 0.0;

export function init(): void {
  macd = new Macd(i32(p_fast()), i32(p_slow()), i32(p_signal()));
  rsi = new Rsi(i32(p_rsi_len()));
  cross = new Cross();
}

export function state(): i32 {
  const close = in_close();
  macd.update(close);
  strength = rsi.update(close);
  crossed = f64(cross.update(macd.macd, macd.signal));
  return isNaN(macd.signal) || isNaN(strength) ? 0 : 1;
}

export function finalize(): void {
  out_macd(macd.macd);
  out_signal(macd.signal);
  out_rsi(strength);
  out_crossed(crossed);
  emitRow();
}

export function reset(): void {
  macd.reset();
  rsi.reset();
  cross.reset();
  strength = NaN;
  crossed = 0.0;
}
```

## The catalog: every builtin and its class

Every name from the kScript catalog and the class or expression that
carries it. Every class ships in `src/sdk/ta.ts`; the page column is
where the group is explained with compiled examples.

**Moving averages and smoothing**

| kScript | Indicator | Where |
| --- | --- | --- |
| `sma`, `mean` | `Sma` | [Moving averages](moving-averages.md) |
| `ema` | `Ema` | [Moving averages](moving-averages.md) |
| `rma` | `Rma` | [Moving averages](moving-averages.md) |
| `wma` | `Wma` | [Moving averages](moving-averages.md) |
| `hma` | `Hma` | [Moving averages](moving-averages.md) |
| `vwma` | `Vwma`, `update(x, volume)` | [Moving averages](moving-averages.md) |
| `alma` | `Alma` | [Moving averages](moving-averages.md) |
| `swma` | `Swma` | [Moving averages](moving-averages.md) |
| `linreg` | `Linreg` | [Moving averages](moving-averages.md) |

**Oscillators and momentum**

| kScript | Indicator | Where |
| --- | --- | --- |
| `rsi` | `Rsi` | [Oscillators](oscillators.md) |
| `wpr` | `Wpr` | [Oscillators](oscillators.md) |
| `cmo` | `Cmo` | [Oscillators](oscillators.md) |
| `tsi` | `Tsi` | [Oscillators](oscillators.md) |
| `macd` | `Macd`, fields `macd`, `signal`, `hist` | [Oscillators](oscillators.md) |
| `stoch` | `Stoch`, fields `k`, `d` | [Oscillators](oscillators.md) |
| `stochastic` | `Stochastic`, fields `k`, `d` | [Oscillators](oscillators.md) |
| `cci` | `Cci` | [Oscillators](oscillators.md) |
| `mfi` | `Mfi` | [Oscillators](oscillators.md), [Volume indicators](volume-indicators.md) |
| `change` | `Change` | [Series functions](series-functions.md) |
| `mom` | `Mom` | [Oscillators](oscillators.md) |
| `roc` | `Roc` | [Oscillators](oscillators.md) |

**Trend and volatility**

| kScript | Indicator | Where |
| --- | --- | --- |
| `adx` | `Adx`, fields `adx`, `plusDi`, `minusDi` | [Trend indicators](trend-indicators.md) |
| `ichimoku` | `Ichimoku`, five fields | [Special indicators](special-indicators.md) |
| `psar` | `Psar` | [Trend indicators](trend-indicators.md) |
| `supertrend` | `Supertrend`, fields `line`, `direction` | [Trend indicators](trend-indicators.md) |
| `tr` | `Tr` | [Trend indicators](trend-indicators.md) |
| `atr` | `Atr` | [Trend indicators](trend-indicators.md) |
| `bb` | `Bb`, fields `basis`, `upper`, `lower` | [Moving averages](moving-averages.md) |
| `keltner` | `Keltner`, fields `basis`, `upper`, `lower` | [Moving averages](moving-averages.md) |
| `donchian` | `Donchian`, fields `basis`, `upper`, `lower` | [Moving averages](moving-averages.md) |
| `stdev`, `stddev` | `Stdev` | [Trend indicators](trend-indicators.md) |
| `variance` | `Variance` | [Trend indicators](trend-indicators.md) |
| `hl2`, `hlc3`, `ohlc4`, `hlcc4` | arithmetic on the declared inputs | [Series functions](series-functions.md) |

**Volume**

| kScript | Indicator | Where |
| --- | --- | --- |
| `obv` | `Obv`, `update(close, volume)` | [Volume indicators](volume-indicators.md) |
| `vwap` | `Vwap`, anchored on the bar's open time | [Special indicators](special-indicators.md) |
| `cum` | `Cum` | [Volume indicators](volume-indicators.md) |

**Statistics**

| kScript | Indicator | Where |
| --- | --- | --- |
| `sum` | `Sum` | [Utility functions](utility-functions.md) |
| `median` | `Median` | [Series functions](series-functions.md) |
| `percentile` | `Percentile` | [Series functions](series-functions.md) |
| `correlation` | `Correlation`, `update(a, b)` | [Series functions](series-functions.md) |
| `zScore` | `Zscore` | [Utility functions](utility-functions.md) |

**Bars and events**

| kScript | Indicator | Where |
| --- | --- | --- |
| `highest`, `lowest` | `Highest`, `Lowest`, field `bars` | [Series functions](series-functions.md) |
| `highestbars`, `lowestbars` | `HighestBars`, `LowestBars`, field `value` | [Series functions](series-functions.md) |
| `pivothigh`, `pivotlow` | `PivotHigh`, `PivotLow` | [Series functions](series-functions.md) |
| `rising`, `falling` | `Rising`, `Falling` | [Series functions](series-functions.md) |
| `valuewhen` | `ValueWhen` | [Series functions](series-functions.md) |
| `barssince` | `BarsSince` | [Series functions](series-functions.md) |
| `crossover`, `crossunder`, `cross` | `Cross`, `+1` / `-1` / `0` | [Series functions](series-functions.md) |
| `fixnan` | `Fixnan` | [Series functions](series-functions.md) |
| `isna` | `isNaN(x)` | [Series functions](series-functions.md) |
| `nz` | `nz(x, replacement)`, a two-line helper | [Series functions](series-functions.md) |

The order book and volume profile scans (`bidSum`, `askSum`, the footprint
accessors) are not classes: they are loops over a celled input on the
[Orderbook functions](orderbook-functions.md) and
[Volume profile](volume-profile.md) pages.

## Conventions (read this once)

These rules hold across the library and explain nearly every edge case.
Each one is the engine's own rule, carried over rather than tidied up.

**Warm-up is `NaN`, with the engine's exceptions.** A windowed class
returns `NaN` until it has a full window and never fabricates an early
value. The classes whose builtin reports a partial window from bar 0 do
the same: `Sum`, `Donchian`, `Ichimoku`, `Cum` (bar 0 is `x`), `Obv`
(bar 0 is 0), and `Stochastic` (0 before its window fills). Decide per
row whether to abstain (`return 0` from `state()`) or to write the `NaN`
and let the chart draw nothing on that bar
([Execution model](../core-concepts/execution-model.md)).

**Windows are strict; accumulators poison.** A class that recomputes over
its window (`Sma`, `Wma`, `Alma`, `Linreg`, `Median`, `Percentile`,
`Variance`, `Stdev`, `Correlation`, `Bb`, `Highest`, `Lowest`, and the
rest of the window family) yields `NaN` while any value inside the window
is not finite and heals as soon as it leaves. A class that carries a
running accumulator (`Ema`, `Rma`, `Atr`, `Rsi`, `Macd`, `Adx`, `Cum`,
`Psar`, `Supertrend`'s ATR) restarts its seed when a non-finite value
arrives before the seed completes, and turns `NaN` for good when one
arrives after, because the engine never reseeds. `Sum` and `Zscore` skip
`NaN` bars instead. Run a sparse source through `Fixnan` or an `isNaN`
guard before an accumulator.

**Column defaults are yours to honor.** kScript read the high for
`highest`, `highestbars`, and `pivothigh`, and the low for their
counterparts; the classes take one value per bar, so pass `in_high()`
or `in_low()` to match, or the close to match `highest(source=d.close)`.

**Classes compose.** `update()` takes any `f64`, including another
class's output (`rsi.update(sma.update(close))`) and any series derived
from a celled input. Warm-up propagates through the composition because
`NaN` propagates through arithmetic.

**Values carry; events do not.** A missing scalar carries the latest
eligible value forward by default (the `missing` policy on the source
decides), so math keeps working. Events are stricter: `Cross.update()`
returns `0` on any bar where either side is `NaN`, `BarsSince` and
`ValueWhen` read a condition you computed (finite and not 0), so stale
data cannot fabricate a signal.

**`Rma` is Wilder, and it is the engine's Wilder.** `Rsi`, `Atr`,
`Keltner`, and `Supertrend` share the same accumulator, so a port that
composed them in kScript reads the same numbers here.

**`reset()` restores everything.** The host replays the forming bar
through `reset()` on every tick; a class or a module-level variable it
forgets is a stale value the replayed bar reads.

## Multi-output indicators

A kScript builtin that returned several streams is a class with several
fields. There are no tuple outputs and nothing indexes into an array:
`update()` returns the primary line, and you read the other fields after
the call and write each to its own output
([Named streams](../core-concepts/named-streams.md)).

| Class | `update()` returns | Fields |
| --- | --- | --- |
| `Bb`, `Keltner`, `Donchian` | `basis` | `basis`, `upper`, `lower` |
| `Macd` | `macd` | `macd`, `signal`, `hist` |
| `Stoch`, `Stochastic` | `k` | `k`, `d` |
| `Supertrend` | `line` | `line`, `direction` |
| `Adx` | `adx` | `adx`, `plusDi`, `minusDi` |
| `Ichimoku` | `tenkan` | `tenkan`, `kijun`, `senkouA`, `senkouB`, `chikou` |
| `Highest`, `Lowest` | the extreme | `bars` (the offset of that extreme) |
| `HighestBars`, `LowestBars` | the offset | `value` (the extreme at that offset) |

Fields feed a band declaration or a box the same way outputs do: write
`bb.upper` and `bb.lower` to two outputs and declare a `box` between
their handles ([Drawing objects](drawing-objects.md)).

## Over microstructure

Every windowed class also runs over order-flow series, which is where the
library stops being a price-only kit. A celled `volume_profile` input
delivers each bar's `[low, high, buy, sell]` tuples; sum the buy and sell
columns into a per-bar delta and the delta is just an `f64` any class can
fold: an `Rsi` over it is delta-RSI, a running sum is cumulative volume
delta. Declaring the celled input switches the derived sheet to the
second runtime contract, and the numeric classes compute exactly as
before.

```typescript
import { input, line, lower, ohlcv, output, param, volume_profile } from "./sdk/declare";
import { in_close, in_profile_capacity, in_profile_cells, in_profile_read } from "./gen/inputs";
import { emitRow, out_cvd, out_delta, out_delta_rsi } from "./gen/outputs";
import { p_period } from "./gen/params";
import { Rsi } from "./sdk/ta";

param("period", 14, { min: 2, max: 200, description: "RSI window over the per-bar delta" });
input("close", ohlcv.close);
input("profile", volume_profile.cells, { max_cells: 512 });
output("delta", line, lower, { color: "#94a3b8", width: 1, description: "Buy minus sell volume across the bar's profile" });
output("delta_rsi", line, lower, { color: "#7c3aed", width: 2, description: "RSI of the per-bar delta" });
output("cvd", line, lower, { color: "#22d3ee", width: 2, description: "Cumulative volume delta" });

const cells = new StaticArray<f64>(in_profile_capacity);
let rsi = new Rsi(14);
let delta: f64 = NaN;
let deltaRsi: f64 = NaN;
let cvd: f64 = 0.0;

export function init(): void {
  rsi = new Rsi(i32(p_period()));
}

export function state(): i32 {
  in_close();
  const n = in_profile_cells();
  if (n < 0) return 0; // this bar carries no block
  delta = 0.0;
  if (n > 0) {
    in_profile_read(i32(changetype<usize>(cells)));
    for (let i = 0; i + 3 < n; i += 4) delta += cells[i + 2] - cells[i + 3];
  }
  cvd += delta;
  deltaRsi = rsi.update(delta);
  return 1;
}

export function finalize(): void {
  out_delta(delta);
  out_delta_rsi(deltaRsi);
  out_cvd(cvd);
  emitRow();
}

export function reset(): void {
  rsi.reset();
  delta = NaN;
  deltaRsi = NaN;
  cvd = 0.0;
}
```

The [Volume profile](volume-profile.md) page has every accessor the
kScript footprint functions offered as a scan over the same tuples; the
[Orderbook functions](orderbook-functions.md) page does the same over
book levels for a book-imbalance average.

## Accuracy as a contract

**Every class is checked bit-exact against the kScript engine.** The
reference is kScript engine 3.0.85 run over a 690-bar 1h BTCUSDT window;
each class is compiled through the real build and executed through the
real runtime over the same bars, and every output is compared per bar:
a maximum absolute deviation of 0 and identical `NaN` placement (a bar
that is `NaN` in the engine is `NaN` here, and only that bar). The
conventions on this page are written down so that comparison holds, and
a change that drifts a class from the engine's numbers fails the build.

**Two honest exceptions.**

- **`Ichimoku.chikou` is the current close.** The engine computes the
  lagging span by reading `close[i + displacement]`, a bar in the future
  of bar `i`, 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 carries the current close on every bar;
  it matches the engine on those last bars only. The other four fields
  match bar for bar.
- **`Vwap` anchors beyond `""`, `"day"`, and a numeric millisecond width
  are ported but unproven on that window.** The `"week"`, `"month"`,
  `"quarter"`, and `"year"` boundaries follow the engine's UTC calendar
  arithmetic, but the 690-bar window does not exercise a deep-history
  quarter or year, and the engine's session-calendar bucketing and RTH
  session filter (venues with a trading-session calendar) are not
  mirrored, since a per-bar class never sees a session calendar.

Everything else in the catalog, warm-up bars included, reads the same
numbers a kScript script read. [From kScript](../migrations/from-kscript.md)
has the loop for comparing a full port against its original on one
market.
