wickra_embed_core/indicators/
ema.rs1use crate::math;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone)]
30pub struct Ema {
31 period: usize,
32 alpha: f64,
33 one_minus_alpha: f64,
36 current: f64,
37 seeded: bool,
38 warm_sum: f64,
42 warm_count: usize,
43}
44
45impl Ema {
46 #[must_use]
50 pub fn new(period: usize) -> Self {
51 let alpha = 2.0 / (period as f64 + 1.0);
52 Self {
53 period,
54 alpha,
55 one_minus_alpha: 1.0 - alpha,
56 current: 0.0,
57 seeded: false,
58 warm_sum: 0.0,
59 warm_count: 0,
60 }
61 }
62
63 #[must_use]
65 pub const fn period(&self) -> usize {
66 self.period
67 }
68
69 #[must_use]
71 pub const fn alpha(&self) -> f64 {
72 self.alpha
73 }
74
75 #[must_use]
77 pub const fn value(&self) -> Option<f64> {
78 if self.seeded {
79 Some(self.current)
80 } else {
81 None
82 }
83 }
84}
85
86impl Indicator for Ema {
87 type Input = f64;
88
89 fn update(&mut self, input: f64) -> Option<f64> {
90 if !input.is_finite() {
91 return self.value();
92 }
93 if self.seeded {
94 let new = math::mul_add(self.alpha, input, self.one_minus_alpha * self.current);
95 self.current = new;
96 return Some(new);
97 }
98 self.warm_sum += input;
99 self.warm_count += 1;
100 if self.warm_count == self.period {
101 let seed = self.warm_sum / self.period as f64;
102 self.current = seed;
103 self.seeded = true;
104 return Some(seed);
105 }
106 None
107 }
108
109 fn reset(&mut self) {
110 self.current = 0.0;
111 self.seeded = false;
112 self.warm_sum = 0.0;
113 self.warm_count = 0;
114 }
115
116 fn warmup_period(&self) -> usize {
117 self.period
118 }
119
120 fn is_ready(&self) -> bool {
121 self.seeded
122 }
123
124 fn name(&self) -> &'static str {
125 "ema"
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use super::Ema;
132 use crate::traits::Indicator;
133
134 #[test]
135 fn alpha_and_seed() {
136 let mut ema = Ema::new(3);
137 assert_eq!(ema.alpha().to_bits(), (2.0_f64 / 4.0).to_bits());
138 assert_eq!(ema.update(1.0), None);
140 assert_eq!(ema.update(2.0), None);
141 assert_eq!(ema.update(3.0), Some(2.0));
142 assert!(ema.is_ready());
143 }
144
145 #[test]
146 fn reset_clears_state() {
147 let mut ema = Ema::new(4);
148 for i in 0..10 {
149 ema.update(f64::from(i));
150 }
151 ema.reset();
152 assert!(!ema.is_ready());
153 assert_eq!(ema.value(), None);
154 }
155
156 #[test]
157 fn byte_parity_with_wickra_core() {
158 let mut ours = Ema::new(12);
159 let mut theirs = wickra_core::Ema::new(12).unwrap();
160 for i in 0..1000 {
161 let x = 100.0 + 15.0 * (f64::from(i) * 0.07).sin() + 0.02 * f64::from(i);
162 let a = ours.update(x);
163 let b = <wickra_core::Ema as wickra_core::Indicator>::update(&mut theirs, x);
164 assert_eq!(a.map(f64::to_bits), b.map(f64::to_bits), "index {i}");
165 }
166 }
167}