Functional Weave
Code in Python

stats.moving-average@2.0.0

impl/python.py

2,430 bytes · the Python 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 math
from typing import List, Sequence

from .math_round_float import round_float  ← from math.round-float ^1.0.0 · built alongside by fune
from .stats_moving_average_types import MovingAverageKind


def _window_mean(values: List[float], end: int, window: int) -> float:
    # Summed afresh, left to right; math.fsum or sum() with a start value
    # would round differently from the other languages.
    total = 0.0
    for i in range(end - window + 1, end + 1):
        total = total + values[i]
    return total / window


def moving_average(values: Sequence[float], window: int, kind: MovingAverageKind, decimals: int) -> List[float]:
    """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).
    """
    if isinstance(values, (str, bytes)) or not isinstance(values, (list, tuple)):
        raise TypeError("values must be a list of numbers")
    for v in values:
        if isinstance(v, bool) or not isinstance(v, (int, float)) or not math.isfinite(v):
            raise TypeError("values must be finite numbers, received %r" % (v,))
    if isinstance(window, bool) or not isinstance(window, int) or window < 1:
        raise ValueError("window must be a whole number of 1 or greater, received %r" % (window,))
    if kind not in ("simple", "exponential"):
        raise ValueError('unknown moving average kind "%s"' % (kind,))
    # Checked here as well as in round_float (same wording), so a series too
    # short to produce any output still refuses bad decimals.
    if isinstance(decimals, bool) or not isinstance(decimals, int) or decimals < 0 or decimals > 12:
        raise ValueError("decimals must be a whole number from 0 to 12, received %r" % (decimals,))

    series = [float(v) for v in values]
    out: List[float] = []
    if len(series) < window:
        return out

    if kind == "simple":
        for end in range(window - 1, len(series)):
            out.append(round_float(_window_mean(series, end, window), decimals))
        return out

    alpha = 2.0 / (window + 1)
    ema = _window_mean(series, window - 1, window)
    out.append(round_float(ema, decimals))
    for i in range(window, len(series)):
        ema = ema + alpha * (series[i] - ema)
        out.append(round_float(ema, decimals))
    return out