use crate::math;
use crate::traits::Indicator;
#[derive(Debug, Clone)]
pub struct Ema {
period: usize,
alpha: f64,
one_minus_alpha: f64,
current: f64,
seeded: bool,
warm_sum: f64,
warm_count: usize,
}
impl Ema {
#[must_use]
pub fn new(period: usize) -> Self {
let alpha = 2.0 / (period as f64 + 1.0);
Self {
period,
alpha,
one_minus_alpha: 1.0 - alpha,
current: 0.0,
seeded: false,
warm_sum: 0.0,
warm_count: 0,
}
}
#[must_use]
pub const fn period(&self) -> usize {
self.period
}
#[must_use]
pub const fn alpha(&self) -> f64 {
self.alpha
}
#[must_use]
pub const fn value(&self) -> Option<f64> {
if self.seeded {
Some(self.current)
} else {
None
}
}
}
impl Indicator for Ema {
type Input = f64;
fn update(&mut self, input: f64) -> Option<f64> {
if !input.is_finite() {
return self.value();
}
if self.seeded {
let new = math::mul_add(self.alpha, input, self.one_minus_alpha * self.current);
self.current = new;
return Some(new);
}
self.warm_sum += input;
self.warm_count += 1;
if self.warm_count == self.period {
let seed = self.warm_sum / self.period as f64;
self.current = seed;
self.seeded = true;
return Some(seed);
}
None
}
fn reset(&mut self) {
self.current = 0.0;
self.seeded = false;
self.warm_sum = 0.0;
self.warm_count = 0;
}
fn warmup_period(&self) -> usize {
self.period
}
fn is_ready(&self) -> bool {
self.seeded
}
fn name(&self) -> &'static str {
"ema"
}
}
#[cfg(test)]
mod tests {
use super::Ema;
use crate::traits::Indicator;
#[test]
fn alpha_and_seed() {
let mut ema = Ema::new(3);
assert_eq!(ema.alpha().to_bits(), (2.0_f64 / 4.0).to_bits());
assert_eq!(ema.update(1.0), None);
assert_eq!(ema.update(2.0), None);
assert_eq!(ema.update(3.0), Some(2.0));
assert!(ema.is_ready());
}
#[test]
fn reset_clears_state() {
let mut ema = Ema::new(4);
for i in 0..10 {
ema.update(f64::from(i));
}
ema.reset();
assert!(!ema.is_ready());
assert_eq!(ema.value(), None);
}
#[test]
fn byte_parity_with_wickra_core() {
let mut ours = Ema::new(12);
let mut theirs = wickra_core::Ema::new(12).unwrap();
for i in 0..1000 {
let x = 100.0 + 15.0 * (f64::from(i) * 0.07).sin() + 0.02 * f64::from(i);
let a = ours.update(x);
let b = <wickra_core::Ema as wickra_core::Indicator>::update(&mut theirs, x);
assert_eq!(a.map(f64::to_bits), b.map(f64::to_bits), "index {i}");
}
}
}