use crate::indicators::Indicator;
use crate::types::Candle;
use crate::utils::ringbuf::RingBuf;
#[derive(Debug, Clone)]
pub struct EMA {
period: usize,
multiplier: f64,
warmup: RingBuf<f64>,
ema: Option<f64>,
}
impl EMA {
pub fn new(period: usize) -> Self {
assert!(period > 0, "period must be > 0");
Self {
period,
multiplier: 2.0 / (period as f64 + 1.0),
warmup: RingBuf::new(period, 0.0),
ema: None,
}
}
#[inline]
fn update(&mut self, value: f64) -> Option<f64> {
if let Some(prev) = self.ema {
let next = (value - prev) * self.multiplier + prev;
self.ema = Some(next);
return self.ema;
}
self.warmup.push(value);
if self.warmup.len() < self.period {
return None;
}
let sum: f64 = self.warmup.iter().copied().sum();
let sma = sum / self.period as f64;
self.ema = Some(sma);
self.ema
}
}
impl Indicator for EMA {
type Output = f64;
fn next(&mut self, candle: &Candle) -> Option<Self::Output> {
self.update(candle.close)
}
fn reset(&mut self) {
self.ema = None;
self.warmup = RingBuf::new(self.period, 0.0);
}
fn warmup_period(&self) -> usize {
self.period
}
fn clone_boxed(&self) -> Box<dyn Indicator<Output = Self::Output>> {
Box::new(self.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn computes_ema_after_warmup() {
let mut ema = EMA::new(3);
let prices = [1.0, 2.0, 3.0, 4.0];
let candles: Vec<Candle> = prices
.iter()
.map(|p| Candle {
timestamp: 0,
open: *p,
high: *p,
low: *p,
close: *p,
volume: 0.0,
})
.collect();
let outputs: Vec<_> = candles.iter().map(|c| ema.next(c)).collect();
assert_eq!(outputs[0], None);
assert_eq!(outputs[1], None);
assert!(outputs[2].is_some());
assert!(outputs[3].is_some());
}
}