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…

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, Oscillators, Trend indicators, Special indicators, Volume indicators, Series functions, Utility functions) 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

ClassConstructPer barReplacesConvention
Smanew Sma(period).update(x)sma, meanplain window mean; NaN until period bars exist and whenever the window holds a non-finite value
Emanew Ema(period).update(x)emathe 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
Rmanew Rma(period).update(x)rmaWilder smoothing, the accumulator inside Rsi and Atr: same seed as Ema, then (prev * (period - 1) + x) / period
Wmanew Wma(period).update(x)wmalinear weights, the newest value weighs period, the oldest 1; strict window
Hmanew Hma(period).update(x)hmawma(2 * wma(x, round(period / 2)) - wma(x, period), round(sqrt(period))); first value at bar period - 1 + round(sqrt(period)) - 1
Vwmanew Vwma(period).update(x, volume)vwmasum(x * volume) / sum(volume); NaN when the volume sum is 0
Almanew Alma(length, offset = 0.85, sigma = 6).update(x)almaGaussian weights centered at offset * (length - 1), width length / sigma; strict window
Swmanew 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
Linregnew Linreg(period, offset = 0).update(x)linregleast-squares line through the window evaluated at period - 1 - offset (offset truncated to an integer); strict window

Statistics and series math

ClassConstructPer barReplacesConvention
Sumnew Sum(period).update(x)sumno warm-up: bar 0 already returns the partial window; NaN inputs are skipped
Mediannew Median(period).update(x)medianmiddle of the sorted window, the mean of the two middle values on an even period; strict window
Percentilenew Percentile(period, pct).update(x)percentilenearest rank: rank = ceil(pct / 100 * period), result sorted[max(0, rank - 1)]; pct clamped to 0..100; strict window
Variancenew Variance(period).update(x)variancepopulation variance (divide by period, not period - 1); strict window
Stdevnew Stdev(period).update(x)stdev, stddevpopulation standard deviation, the square root of Variance; strict window
Zscorenew 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
Correlationnew Correlation(period).update(a, b)correlationPearson correlation of two series; NaN when either window holds a non-finite value or the denominator is 0
Changenew Change(n = 1).update(x)changex - x[n]; NaN for the first n bars
Momnew Mom(n).update(x)momthe same math as Change; kScript exposes both names
Rocnew Roc(period).update(x)roc(x - x[n]) / x[n] * 100; NaN for the first n bars and when x[n] is 0
Cumnew Cum().update(x)cumrunning sum from bar 0; a non-finite bar makes it NaN for good
Fixnannew Fixnan().update(x)fixnanrepeats the last finite value over a non-finite bar; NaN until the first finite value

Oscillators and momentum

ClassConstructPer barReplacesConvention
Rsinew Rsi(period).update(x)rsiWilder RSI; bar 0 feeds nothing, first value at bar period; a zero average loss returns 100, so a flat window is 100
Cmonew Cmo(length).update(x)cmo100 * (up - down) / (up + down) over the last length changes; first value at bar length; a zero total returns 0
Tsinew Tsi(short = 13, long = 25).update(x)tsidouble 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
Ccinew Cci(period = 20, constant = 0.015).update(high, low, close)cciover typical price (high + low + close) / 3; first value at bar period - 1; 0 when the mean deviation is 0
Mfinew Mfi(period = 14).update(high, low, close, volume)mfimoney flow over typical price times volume; first value at bar period; a zero negative flow returns 100
Wprnew Wpr(length = 14).update(high, low, close)wprWilliams %R; first value at bar length - 1; a flat window returns 0
Stochnew Stoch(periodK, smoothK, periodD).update(high, low, close) returns kstochfields 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
Stochasticnew Stochastic(kPeriod = 14, kSmoothing = 3, dPeriod = 3).update(high, low, close) returns kstochasticthe 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
Macdnew Macd(fast = 12, slow = 26, signal = 9).update(x) returns macdmacdfields macd, signal, hist; the line appears at bar slow - 1, the signal at bar slow + signal - 2; hist = macd - signal
Obvnew Obv().update(close, volume)obvon-balance volume; bar 0 returns 0; an unchanged close adds nothing

Ranges and bands

ClassConstructPer barReplacesConvention
Trnew Tr().update(high, low, close)trmax(high - low, abs(high - prevClose), abs(low - prevClose)); bar 0 is plain high - low
Atrnew Atr(period = 14).update(high, low, close)atrRma of Tr; first value at bar period - 1; a non-finite range after the seed makes it NaN for good
Bbnew Bb(period, mult).update(x) returns basisbbfields basis, upper, lower; Sma basis and population Stdev width; all three NaN while the window holds a non-finite value
Keltnernew Keltner(period, mult, atrPeriod).update(x, high, low, close) returns basiskeltnerfields 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
Donchiannew Donchian(period = 12).update(high, low) returns basisdonchianfields 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
Highestnew Highest(period = 12).update(x)highestthe 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
Lowestnew Lowest(period = 12).update(x)lowestthe window minimum; field bars is the lowestbars offset; pass the low for the default column
HighestBarsnew HighestBars(period).update(x)highestbarsthe offset as the primary value (0 or negative); field value is the matching high
LowestBarsnew LowestBars(period).update(x)lowestbarsthe offset as the primary value; field value is the matching low

Trend systems

ClassConstructPer barReplacesConvention
Adxnew Adx(period = 14).update(high, low, close) returns adxadxfields 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
Ichimokunew Ichimoku(conversionPeriod = 9, basePeriod = 26, laggingSpanPeriod = 52, displacement = 26).update(high, low, close) returns tenkanichimokufields 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)
Psarnew Psar(start = 0.02, increment = 0.02, maxValue = 0.2).update(high, low, close)psarthe 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
Supertrendnew Supertrend(factor, atrPeriod).update(high, low, close) returns linesupertrendfields 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
Vwapnew Vwap(anchor = "", price = "hlc3").update(open, high, low, close, volume, tsMs = NaN)vwapanchor 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

ClassConstructPer barReplacesConvention
Risingnew Rising(period).update(x)rising1 when x is strictly above every one of the previous period values, else 0; NaN while the bar index is below period
Fallingnew Falling(period).update(x)fallingthe mirror of Rising
PivotHighnew PivotHigh(leftbars, rightbars).update(high)pivothighthe 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
PivotLownew PivotLow(leftbars, rightbars).update(low)pivotlowthe mirror of PivotHigh
ValueWhennew ValueWhen(occurrence).update(condition, x)valuewhenx 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
BarsSincenew BarsSince().update(condition)barssincebars since the condition was last true, 0 on a true bar; NaN until the first true bar
Crossnew Cross().update(a, b): i32crossover, 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):

{
  "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": "" }
  ]
}
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

kScriptIndicatorWhere
sma, meanSmaMoving averages
emaEmaMoving averages
rmaRmaMoving averages
wmaWmaMoving averages
hmaHmaMoving averages
vwmaVwma, update(x, volume)Moving averages
almaAlmaMoving averages
swmaSwmaMoving averages
linregLinregMoving averages

Oscillators and momentum

kScriptIndicatorWhere
rsiRsiOscillators
wprWprOscillators
cmoCmoOscillators
tsiTsiOscillators
macdMacd, fields macd, signal, histOscillators
stochStoch, fields k, dOscillators
stochasticStochastic, fields k, dOscillators
cciCciOscillators
mfiMfiOscillators, Volume indicators
changeChangeSeries functions
momMomOscillators
rocRocOscillators

Trend and volatility

kScriptIndicatorWhere
adxAdx, fields adx, plusDi, minusDiTrend indicators
ichimokuIchimoku, five fieldsSpecial indicators
psarPsarTrend indicators
supertrendSupertrend, fields line, directionTrend indicators
trTrTrend indicators
atrAtrTrend indicators
bbBb, fields basis, upper, lowerMoving averages
keltnerKeltner, fields basis, upper, lowerMoving averages
donchianDonchian, fields basis, upper, lowerMoving averages
stdev, stddevStdevTrend indicators
varianceVarianceTrend indicators
hl2, hlc3, ohlc4, hlcc4arithmetic on the declared inputsSeries functions

Volume

kScriptIndicatorWhere
obvObv, update(close, volume)Volume indicators
vwapVwap, anchored on the bar's open timeSpecial indicators
cumCumVolume indicators

Statistics

kScriptIndicatorWhere
sumSumUtility functions
medianMedianSeries functions
percentilePercentileSeries functions
correlationCorrelation, update(a, b)Series functions
zScoreZscoreUtility functions

Bars and events

kScriptIndicatorWhere
highest, lowestHighest, Lowest, field barsSeries functions
highestbars, lowestbarsHighestBars, LowestBars, field valueSeries functions
pivothigh, pivotlowPivotHigh, PivotLowSeries functions
rising, fallingRising, FallingSeries functions
valuewhenValueWhenSeries functions
barssinceBarsSinceSeries functions
crossover, crossunder, crossCross, +1 / -1 / 0Series functions
fixnanFixnanSeries functions
isnaisNaN(x)Series functions
nznz(x, replacement), a two-line helperSeries functions

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 and Volume profile 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).

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

Classupdate() returnsFields
Bb, Keltner, Donchianbasisbasis, upper, lower
Macdmacdmacd, signal, hist
Stoch, Stochastickk, d
Supertrendlineline, direction
Adxadxadx, plusDi, minusDi
Ichimokutenkantenkan, kijun, senkouA, senkouB, chikou
Highest, Lowestthe extremebars (the offset of that extreme)
HighestBars, LowestBarsthe offsetvalue (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).

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.

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 page has every accessor the kScript footprint functions offered as a scan over the same tuples; the Orderbook functions 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 has the loop for comparing a full port against its original on one market.