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,NaNuntil 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 throughreset()on every tick.- Periods are
i32, params aref64. Construct ininit()from a param (new Sma(i32(p_period()))), keep the object in a module-levellet, update it once per bar instate(), and call.reset()on it from yourreset(). A period below 1 is clamped to 1 (exceptDonchian, 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):
{
"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
| kScript | Indicator | Where |
|---|---|---|
sma, mean | Sma | Moving averages |
ema | Ema | Moving averages |
rma | Rma | Moving averages |
wma | Wma | Moving averages |
hma | Hma | Moving averages |
vwma | Vwma, update(x, volume) | Moving averages |
alma | Alma | Moving averages |
swma | Swma | Moving averages |
linreg | Linreg | Moving averages |
Oscillators and momentum
| kScript | Indicator | Where |
|---|---|---|
rsi | Rsi | Oscillators |
wpr | Wpr | Oscillators |
cmo | Cmo | Oscillators |
tsi | Tsi | Oscillators |
macd | Macd, fields macd, signal, hist | Oscillators |
stoch | Stoch, fields k, d | Oscillators |
stochastic | Stochastic, fields k, d | Oscillators |
cci | Cci | Oscillators |
mfi | Mfi | Oscillators, Volume indicators |
change | Change | Series functions |
mom | Mom | Oscillators |
roc | Roc | Oscillators |
Trend and volatility
| kScript | Indicator | Where |
|---|---|---|
adx | Adx, fields adx, plusDi, minusDi | Trend indicators |
ichimoku | Ichimoku, five fields | Special indicators |
psar | Psar | Trend indicators |
supertrend | Supertrend, fields line, direction | Trend indicators |
tr | Tr | Trend indicators |
atr | Atr | Trend indicators |
bb | Bb, fields basis, upper, lower | Moving averages |
keltner | Keltner, fields basis, upper, lower | Moving averages |
donchian | Donchian, fields basis, upper, lower | Moving averages |
stdev, stddev | Stdev | Trend indicators |
variance | Variance | Trend indicators |
hl2, hlc3, ohlc4, hlcc4 | arithmetic on the declared inputs | Series functions |
Volume
| kScript | Indicator | Where |
|---|---|---|
obv | Obv, update(close, volume) | Volume indicators |
vwap | Vwap, anchored on the bar's open time | Special indicators |
cum | Cum | Volume indicators |
Statistics
| kScript | Indicator | Where |
|---|---|---|
sum | Sum | Utility functions |
median | Median | Series functions |
percentile | Percentile | Series functions |
correlation | Correlation, update(a, b) | Series functions |
zScore | Zscore | Utility functions |
Bars and events
| kScript | Indicator | Where |
|---|---|---|
highest, lowest | Highest, Lowest, field bars | Series functions |
highestbars, lowestbars | HighestBars, LowestBars, field value | Series functions |
pivothigh, pivotlow | PivotHigh, PivotLow | Series functions |
rising, falling | Rising, Falling | Series functions |
valuewhen | ValueWhen | Series functions |
barssince | BarsSince | Series functions |
crossover, crossunder, cross | Cross, +1 / -1 / 0 | Series functions |
fixnan | Fixnan | Series functions |
isna | isNaN(x) | Series functions |
nz | nz(x, replacement), a two-line helper | Series 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).
| 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).
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.chikouis the current close. The engine computes the lagging span by readingclose[i + displacement], a bar in the future of bari, and only falls back to the current close on the lastdisplacementbars 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.Vwapanchors 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.