wickra_embed_core/indicators/
sma.rs1use crate::ring::Ring;
5use crate::traits::Indicator;
6
7const RECOMPUTE_EVERY: usize = 16;
12
13#[derive(Debug, Clone)]
32pub struct Sma<const N: usize> {
33 buf: Ring<N>,
34 sum: f64,
35 updates_since_recompute: usize,
36}
37
38impl<const N: usize> Sma<N> {
39 #[must_use]
41 pub const fn new() -> Self {
42 Self {
43 buf: Ring::new(),
44 sum: 0.0,
45 updates_since_recompute: 0,
46 }
47 }
48
49 #[must_use]
51 pub fn value(&self) -> Option<f64> {
52 if self.buf.is_full() {
53 Some(self.sum / N as f64)
54 } else {
55 None
56 }
57 }
58}
59
60impl<const N: usize> Default for Sma<N> {
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl<const N: usize> Indicator for Sma<N> {
67 type Input = f64;
68
69 fn update(&mut self, input: f64) -> Option<f64> {
70 if !input.is_finite() {
71 return self.value();
72 }
73 match self.buf.push(input) {
76 Some(old) => {
77 self.sum -= old;
78 self.sum += input;
79 }
80 None => {
81 self.sum += input;
82 }
83 }
84 self.updates_since_recompute += 1;
85 if self.updates_since_recompute >= RECOMPUTE_EVERY * N {
86 self.sum = self.buf.chrono_sum();
87 self.updates_since_recompute = 0;
88 }
89 self.value()
90 }
91
92 fn reset(&mut self) {
93 *self = Self::new();
94 }
95
96 fn warmup_period(&self) -> usize {
97 N
98 }
99
100 fn is_ready(&self) -> bool {
101 self.buf.is_full()
102 }
103
104 fn name(&self) -> &'static str {
105 "sma"
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::Sma;
112 use crate::traits::Indicator;
113
114 #[test]
115 fn warmup_then_mean() {
116 let mut sma = Sma::<4>::new();
117 assert!(!sma.is_ready());
118 for x in [10.0, 20.0, 30.0] {
119 assert_eq!(sma.update(x), None);
120 }
121 assert_eq!(sma.update(40.0), Some(25.0));
122 assert!(sma.is_ready());
123 assert_eq!(sma.warmup_period(), 4);
124 }
125
126 #[test]
127 fn reset_clears_state() {
128 let mut sma = Sma::<3>::new();
129 for x in [1.0, 2.0, 3.0, 4.0] {
130 sma.update(x);
131 }
132 assert!(sma.is_ready());
133 sma.reset();
134 assert!(!sma.is_ready());
135 assert_eq!(sma.value(), None);
136 }
137
138 #[test]
139 fn non_finite_is_ignored() {
140 let mut sma = Sma::<2>::new();
141 sma.update(2.0);
142 let ready = sma.update(4.0);
143 assert_eq!(ready, Some(3.0));
144 assert_eq!(sma.update(f64::NAN), Some(3.0));
146 assert_eq!(sma.update(f64::INFINITY), Some(3.0));
147 }
148
149 #[test]
152 fn byte_parity_with_wickra_core() {
153 let mut ours = Sma::<20>::new();
154 let mut theirs = wickra_core::Sma::new(20).unwrap();
155 for i in 0..2000 {
156 let x = 100.0 + 25.0 * (f64::from(i) * 0.05).sin() + 0.01 * f64::from(i);
157 let a = ours.update(x);
158 let b = <wickra_core::Sma as wickra_core::Indicator>::update(&mut theirs, x);
159 assert_eq!(
160 a.map(f64::to_bits),
161 b.map(f64::to_bits),
162 "divergence at index {i}"
163 );
164 }
165 }
166}