impl/typescript.ts
2,245 bytes · the TypeScript implementation · view raw
Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { roundFloat } from "./math_round_float.ts"; ← from math.round-float ^1.0.0 · built alongside by fune
import { type MovingAverageKind } from "./stats_moving_average_types.ts";
// Summed afresh, left to right, so the result never depends on the history
// a running sum would carry.
function windowMean(values: readonly number[], end: number, window: number): number {
let sum = 0;
for (let i = end - window + 1; i <= end; i++) sum += values[i];
return sum / window;
}
/**
* Simple or exponential moving average, one value per full window.
*
* The exponential average is seeded with the simple average of the first
* window and carried unrounded; only the returned values are rounded, by
* math.round-float on the exact value of each double (2.675 gives 2.67).
*/
export function movingAverage(
values: readonly number[],
window: number,
kind: MovingAverageKind,
decimals: number,
): readonly number[] {
if (!Array.isArray(values)) throw new TypeError("values must be a list of numbers");
for (const v of values) {
if (typeof v !== "number" || !Number.isFinite(v)) {
throw new TypeError(`values must be finite numbers, received ${v}`);
}
}
if (!Number.isInteger(window) || window < 1) {
throw new RangeError(`window must be a whole number of 1 or greater, received ${window}`);
}
if (kind !== "simple" && kind !== "exponential") {
throw new RangeError(`unknown moving average kind "${kind}"`);
}
// Checked here as well as in roundFloat (same wording), so a series too
// short to produce any output still refuses bad decimals.
if (!Number.isInteger(decimals) || decimals < 0 || decimals > 12) {
throw new RangeError(`decimals must be a whole number from 0 to 12, received ${decimals}`);
}
const out: number[] = [];
if (values.length < window) return out;
if (kind === "simple") {
for (let end = window - 1; end < values.length; end++) {
out.push(roundFloat(windowMean(values, end, window), decimals));
}
return out;
}
const alpha = 2 / (window + 1);
let ema = windowMean(values, window - 1, window);
out.push(roundFloat(ema, decimals));
for (let i = window; i < values.length; i++) {
ema = ema + alpha * (values[i] - ema);
out.push(roundFloat(ema, decimals));
}
return out;
}