---
title: "Utility functions"
description: "Statistics over a window, cross detection, level helpers, and value validation: the building blocks the kScript (legacy) utility page grouped together. In…"
order: 52
section: "functions"
---

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

# Utility functions

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](series-functions.md) 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.

```typescript
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

```typescript
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.
