oxihuman_core/
moving_avg_calc.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone)]
9pub struct SimpleMaCalc {
10 window: Vec<f64>,
11 size: usize,
12 pos: usize,
13 filled: bool,
14 sum: f64,
15}
16
17impl SimpleMaCalc {
18 pub fn new(size: usize) -> Self {
19 assert!(size > 0, "window size must be positive");
20 SimpleMaCalc {
21 window: vec![0.0; size],
22 size,
23 pos: 0,
24 filled: false,
25 sum: 0.0,
26 }
27 }
28
29 pub fn update(&mut self, value: f64) -> f64 {
30 self.sum -= self.window[self.pos];
31 self.window[self.pos] = value;
32 self.sum += value;
33 self.pos += 1;
34 if self.pos >= self.size {
35 self.pos = 0;
36 self.filled = true;
37 }
38 self.current()
39 }
40
41 pub fn current(&self) -> f64 {
42 let count = if self.filled {
43 self.size
44 } else {
45 self.pos.max(1)
46 };
47 self.sum / count as f64
48 }
49
50 pub fn is_ready(&self) -> bool {
51 self.filled
52 }
53
54 pub fn window_size(&self) -> usize {
55 self.size
56 }
57}
58
59#[derive(Debug, Clone)]
61pub struct EmaCalc {
62 alpha: f64,
63 value: Option<f64>,
64}
65
66impl EmaCalc {
67 pub fn new(alpha: f64) -> Self {
69 let alpha = alpha.clamp(0.0, 1.0);
70 EmaCalc { alpha, value: None }
71 }
72
73 pub fn from_period(period: usize) -> Self {
75 let alpha = 2.0 / (period as f64 + 1.0);
76 EmaCalc::new(alpha)
77 }
78
79 pub fn update(&mut self, value: f64) -> f64 {
80 let ema = match self.value {
81 None => value,
82 Some(prev) => self.alpha * value + (1.0 - self.alpha) * prev,
83 };
84 self.value = Some(ema);
85 ema
86 }
87
88 pub fn current(&self) -> Option<f64> {
89 self.value
90 }
91
92 pub fn alpha(&self) -> f64 {
93 self.alpha
94 }
95}
96
97pub fn sma_batch(data: &[f64], window: usize) -> Vec<f64> {
98 let mut ma = SimpleMaCalc::new(window);
99 data.iter().map(|&v| ma.update(v)).collect()
100}
101
102pub fn ema_batch(data: &[f64], period: usize) -> Vec<f64> {
103 let mut ema = EmaCalc::from_period(period);
104 data.iter().map(|&v| ema.update(v)).collect()
105}
106
107pub fn ma_crossover(fast: &[f64], slow: &[f64]) -> Vec<f64> {
108 fast.iter().zip(slow.iter()).map(|(f, s)| f - s).collect()
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
116 fn test_sma_single() {
117 let mut ma = SimpleMaCalc::new(3);
118 let v = ma.update(10.0);
119 assert!((v - 10.0).abs() < 1e-10, );
120 }
121
122 #[test]
123 fn test_sma_window() {
124 let mut ma = SimpleMaCalc::new(3);
125 ma.update(1.0);
126 ma.update(2.0);
127 let v = ma.update(3.0);
128 assert!((v - 2.0).abs() < 1e-10 ,);
129 }
130
131 #[test]
132 fn test_sma_ready() {
133 let mut ma = SimpleMaCalc::new(3);
134 ma.update(1.0);
135 ma.update(2.0);
136 assert!(!ma.is_ready() ,);
137 ma.update(3.0);
138 assert!(ma.is_ready() ,);
139 }
140
141 #[test]
142 fn test_ema_first_value() {
143 let mut ema = EmaCalc::new(0.5);
144 let v = ema.update(100.0);
145 assert!((v - 100.0).abs() < 1e-10 ,);
146 }
147
148 #[test]
149 fn test_ema_smoothing() {
150 let mut ema = EmaCalc::new(0.5);
151 ema.update(100.0);
152 let v = ema.update(0.0);
153 assert!((v - 50.0).abs() < 1e-10 ,);
154 }
155
156 #[test]
157 fn test_ema_from_period() {
158 let ema = EmaCalc::from_period(9);
159 assert!((ema.alpha() - 0.2).abs() < 1e-10, );
160 }
161
162 #[test]
163 fn test_sma_batch_length() {
164 let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
165 let result = sma_batch(&data, 3);
166 assert_eq!(result.len(), 5);
167 }
168
169 #[test]
170 fn test_ema_batch_length() {
171 let data = vec![1.0, 2.0, 3.0, 4.0];
172 let result = ema_batch(&data, 3);
173 assert_eq!(result.len(), 4);
174 }
175
176 #[test]
177 fn test_crossover_length() {
178 let fast = vec![1.0, 2.0, 3.0];
179 let slow = vec![1.5, 1.5, 1.5];
180 let cross = ma_crossover(&fast, &slow);
181 assert_eq!(cross.len(), 3);
182 assert!((cross[2] - 1.5).abs() < 1e-10 ,);
183 }
184}