Statistics over a window, cross detection, level helpers, and value
validation: the building blocks the kScript (legacy) utility page grouped
together. In Indicators every one of them ships as a class in
src/sdk/ta.ts (Highest, Lowest, Sum, Stdev, Donchian,
Cross), each matching the kScript engine bar for bar, and the value
checks are AssemblyScript builtins. The same classes appear on the
Series functions page under their longer names;
this page is the short reference and one compiled module that runs all
of them.
| kScript | Indicator form | Notes |
|---|---|---|
lowest(source, period) | new Lowest(period) over in_low(), .update(x) | the lowest value in the window; field bars is its offset |
highest(source, period) | new Highest(period) over in_high(), .update(x) | the highest value in the window; field bars is its offset |
sum(source, period) | new Sum(period), .update(x) | the window total; no warm-up, a partial window on the first bars |
stddev(source, period), stdev(source, period) | new Stdev(period), .update(x) | population deviation, strict window |
donchian(source, period) | new Donchian(period), .update(high, low), field basis | the channel midpoint (upper and lower too) |
crossover(a, b) | cross.update(a, b) == 1 | new Cross() |
crossunder(a, b) | cross.update(a, b) == -1 | |
cross(a, b) | cross.update(a, b) != 0 | |
isnan(x) | isNaN(x) | an AssemblyScript builtin |
isnum(x) | isFinite(x) | finite and not NaN |
Statistics
Highest and Lowest
new Highest(period) and new Lowest(period), .update(x) returns the
window's extreme and the field bars holds how many bars back it sits,
as 0 or a negative number (0 = this bar, -3 = three bars ago; the
newest bar wins a tie). Both are NaN until period bars exist and
whenever a non-finite value sits inside the window. kScript's
priceIndex argument chose the column; here you choose by feeding the
class the series you want, so a 20-bar high is a Highest(20) over the
high input and a 20-bar low a Lowest(20) over the low input.
Sum
new Sum(period), .update(x) returns the total of the last period
values. There is no warm-up: bar 0 already returns the sum of the bars
seen so far (a partial window), a NaN input is skipped rather than
poisoning the total, and the window keeps sliding.
import { Sum } from "./sdk/ta";
let volumeSum = new Sum(20);
// In state(): finite from bar 0, a partial window until 20 bars exist.
// const total = volumeSum.update(in_volume());That partial-window rule is the engine's, and it differs from the strict
window Stdev keeps: a 20-bar Sum draws from the first bar, a 20-bar
Stdev draws from bar 19.
Stdev
new Stdev(period), .update(x). The population standard deviation
(divide by period) over a strict window, NaN until full and NaN
while any sample in the window is not finite. It is kScript's stdev
and its stddev in one class: on finite data the two builtins compute
the same numbers in the same order, and Indicator inputs never carry the
sparse samples they differed on.
Donchian
new Donchian(period), .update(high, low) returns the channel midpoint
(windowHigh + windowLow) / 2 and keeps basis, upper (the window
high), and lower (the window low). Finite from the first bar, because
the window is partial at the start; the current bar's high and low always
take part, while a NaN high or low on an older bar is skipped. The
period is used as given (the engine does not clamp it here).
Cross detection
Cross ships: new Cross(), .update(a, b): i32, +1 on the bar a
crosses above b, -1 on the bar it crosses below, 0 otherwise
(including the first bar and any bar with a NaN side). Call it once per
bar per pair and keep the result. Use == 1 for bullish signals, == -1
for bearish, != 0 for any direction change, and combine the signal with
a trend read before acting on it.
Value checks
isNaN(x) is true for NaN; isFinite(x) is true for a real, finite
number (neither NaN nor infinite). Both are AssemblyScript builtins on
f64; there is no na type, so a missing value is NaN everywhere and
these two are the whole vocabulary. Validate inputs before a division and
your outputs never carry an infinity into a metric.
Every utility in one module
import { input, line, lower, none, ohlcv, output, overlay, param } from "./sdk/declare";
import { in_close, in_high, in_low, in_volume } from "./gen/inputs";
import {
emitRow,
out_any_cross,
out_bearish,
out_bullish,
out_donchian_mid,
out_high20,
out_low20,
out_valid,
out_volatility,
out_volume_sum,
} from "./gen/outputs";
import { p_period } from "./gen/params";
import { Cross, Donchian, Highest, Lowest, Sma, Stdev, Sum } from "./sdk/ta";
param("period", 20, { min: 2, max: 400 });
input("close", ohlcv.close);
input("high", ohlcv.high);
input("low", ohlcv.low);
input("volume", ohlcv.volume);
output("low20", line, overlay, { color: "#dc2626", width: 1, description: "Lowest low over the period" });
output("high20", line, overlay, { color: "#16a34a", width: 1, description: "Highest high over the period" });
output("donchian_mid", line, overlay, { color: "#94a3b8", width: 1, description: "Donchian midpoint" });
output("volume_sum", line, lower, { color: "#0891b2", description: "Volume summed over the period" });
output("volatility", line, lower, { color: "#7c3aed", description: "Standard deviation of the close" });
output("bullish", none, lower, { description: "1 on the bar the fast average crosses above the slow" });
output("bearish", none, lower, { description: "1 on the bar it crosses below" });
output("any_cross", none, lower, { description: "1 on either" });
output("valid", none, lower, { description: "1 when every input on the bar is a finite number" });
let highs = new Highest(20);
let lows = new Lowest(20);
let volumeSum = new Sum(20);
let stdev = new Stdev(20);
let donchian = new Donchian(20);
let fast = new Sma(5);
let slow = new Sma(20);
const cross = new Cross();
let highValue: f64 = NaN;
let lowValue: f64 = NaN;
let sumValue: f64 = NaN;
let volatility: f64 = NaN;
let crossed: i32 = 0;
let valid: f64 = 0.0;
export function init(): void {
const period = i32(p_period());
highs = new Highest(period);
lows = new Lowest(period);
volumeSum = new Sum(period);
stdev = new Stdev(period);
donchian = new Donchian(period);
fast = new Sma(5);
slow = new Sma(period);
}
export function state(): i32 {
const close = in_close();
const high = in_high();
const low = in_low();
const volume = in_volume();
valid = isFinite(close) && isFinite(high) && isFinite(low) && isFinite(volume) ? 1.0 : 0.0;
highValue = highs.update(high);
lowValue = lows.update(low);
sumValue = volumeSum.update(volume);
volatility = stdev.update(close);
donchian.update(high, low);
crossed = cross.update(fast.update(close), slow.update(close));
return 1;
}
export function finalize(): void {
out_low20(lowValue);
out_high20(highValue);
out_donchian_mid(donchian.basis);
out_volume_sum(sumValue);
out_volatility(volatility);
out_bullish(crossed == 1 ? 1.0 : 0.0);
out_bearish(crossed == -1 ? 1.0 : 0.0);
out_any_cross(crossed != 0 ? 1.0 : 0.0);
out_valid(valid);
emitRow();
}
export function reset(): void {
highs.reset();
lows.reset();
volumeSum.reset();
stdev.reset();
donchian.reset();
fast.reset();
slow.reset();
cross.reset();
highValue = NaN;
lowValue = NaN;
sumValue = NaN;
volatility = NaN;
crossed = 0;
valid = 0.0;
}Practices that carry over
- Lookback periods. Choose them for the interval you trade: short
windows for scalping and lower timeframes, longer ones for swing
context. Declare the range on the param (
min,max) and size buffers frommaxso the module never allocates per bar. - Performance. Every class allocates in its constructor and never in
update(). Compute a statistic once per bar and reuse the return value or field rather than constructing a second object over the same series. - Cross detection. Combine a cross with a trend read (
Adx, a slope, a higher-timeframe fold) before treating it as a signal; in a chop the cross flips every few bars. - Donchian channels. Breakout logic in trends, support and resistance in ranges: the same two outputs, different rules on top.
- Data validation. Check
isFinitebefore a division, and writeNaNrather than a made-up number when a value has no answer; aNaNdraws nothing and never trips an alert.