Functional Weave
Code in TypeScript

stats.moving-average@2.0.0

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;
}