Exponential Moving Average Helper

Track a smoothed moving average with a period-derived smoothing factor, plus a windowed simple average for comparison.

PythonData Integrity

Code

snippet
from collections.abc import Iterable
from dataclasses import dataclass, field


@dataclass
class EmaTracker:
    period: int
    value: float | None = None
    alpha: float = field(init=False, default=0.0)

    def __post_init__(self) -> None:
        if self.period < 1:
            raise ValueError("period must be at least 1")
        self.alpha = 2.0 / (self.period + 1)

    def update(self, sample: float) -> float:
        if self.value is None:
            self.value = sample
        else:
            self.value = self.alpha * sample + (1 - self.alpha) * self.value
        return self.value

    def reset(self) -> None:
        self.value = None


def exponential_moving_average(values: Iterable[float], period: int) -> list[float]:
    tracker = EmaTracker(period)
    return [tracker.update(value) for value in values]


def simple_moving_average(values: list[float], window: int) -> list[float | None]:
    if window < 1:
        raise ValueError("window must be at least 1")
    smoothed: list[float | None] = []
    for index in range(len(values)):
        if index + 1 < window:
            smoothed.append(None)
        else:
            chunk = values[index + 1 - window : index + 1]
            smoothed.append(sum(chunk) / window)
    return smoothed


def main() -> None:
    samples = [10.0, 12.0, 11.0, 15.0, 14.0, 16.0]
    print("ema:", [round(value, 3) for value in exponential_moving_average(samples, 3)])
    print("sma:", simple_moving_average(samples, 3))


if __name__ == "__main__":
    main()