Utility functions

Statistics over a window, cross detection, level helpers, and value validation: the building blocks the kScript (legacy) utility page grouped together. In…

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.

kScriptIndicator formNotes
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 basisthe channel midpoint (upper and lower too)
crossover(a, b)cross.update(a, b) == 1new 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 from max so 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 isFinite before a division, and write NaN rather than a made-up number when a value has no answer; a NaN draws nothing and never trips an alert.