quantwave_core/indicators/
smoothing.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3use serde::{Deserialize, Serialize};
4use std::collections::VecDeque;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct SMA {
9 period: usize,
10 window: VecDeque<f64>,
11 sum: f64,
12}
13
14impl SMA {
15 pub fn new(period: usize) -> Self {
16 Self {
17 period,
18 window: VecDeque::with_capacity(period),
19 sum: 0.0,
20 }
21 }
22}
23
24impl From<usize> for SMA {
25 fn from(period: usize) -> Self {
26 Self::new(period)
27 }
28}
29
30impl Next<f64> for SMA {
31 type Output = f64;
32
33 fn next(&mut self, input: f64) -> Self::Output {
34 self.window.push_back(input);
35 self.sum += input;
36
37 if self.window.len() > self.period && let Some(oldest) = self.window.pop_front() {
38 self.sum -= oldest;
39 }
40
41 self.sum / self.window.len() as f64
42 }
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct EMA {
48 _period: usize,
49 alpha: f64,
50 current_ema: Option<f64>,
51}
52
53impl EMA {
54 pub fn new(period: usize) -> Self {
55 Self {
56 _period: period,
57 alpha: 2.0 / (period as f64 + 1.0),
58 current_ema: None,
59 }
60 }
61}
62
63impl From<usize> for EMA {
64 fn from(period: usize) -> Self {
65 Self::new(period)
66 }
67}
68
69impl Next<f64> for EMA {
70 type Output = f64;
71
72 fn next(&mut self, input: f64) -> Self::Output {
73 match self.current_ema {
74 Some(prev_ema) => {
75 let ema = self.alpha * input + (1.0 - self.alpha) * prev_ema;
76 self.current_ema = Some(ema);
77 ema
78 }
79 None => {
80 self.current_ema = Some(input);
81 input
82 }
83 }
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct WMA {
90 period: usize,
91 window: VecDeque<f64>,
92}
93
94impl WMA {
95 pub fn new(period: usize) -> Self {
96 Self {
97 period,
98 window: VecDeque::with_capacity(period),
99 }
100 }
101}
102
103impl From<usize> for WMA {
104 fn from(period: usize) -> Self {
105 Self::new(period)
106 }
107}
108
109impl Next<f64> for WMA {
110 type Output = f64;
111
112 fn next(&mut self, input: f64) -> Self::Output {
113 self.window.push_back(input);
114 if self.window.len() > self.period {
115 self.window.pop_front();
116 }
117
118 let mut weight_sum = 0.0;
119 let mut weighted_val_sum = 0.0;
120
121 for (i, &val) in self.window.iter().enumerate() {
122 let weight = (i + 1) as f64;
123 weighted_val_sum += val * weight;
124 weight_sum += weight;
125 }
126
127 if weight_sum == 0.0 {
128 0.0
129 } else {
130 weighted_val_sum / weight_sum
131 }
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use crate::test_utils::{assert_indicator_parity, load_gold_standard};
139
140 #[test]
141 fn test_sma_gold_standard() {
142 let case = load_gold_standard("sma_5");
143 let sma = SMA::new(3); assert_indicator_parity(sma, &case.input, &case.expected);
145 }
146
147 #[test]
148 fn test_ema_basic() {
149 let mut ema = EMA::new(3);
150 assert_eq!(ema.next(10.0), 10.0);
151 approx::assert_relative_eq!(ema.next(12.0), 11.0); }
153
154 #[test]
155 fn test_wma_basic() {
156 let mut wma = WMA::new(3);
157 assert_eq!(wma.next(1.0), 1.0);
158 approx::assert_relative_eq!(wma.next(2.0), 1.6666666666, epsilon = 1e-6); approx::assert_relative_eq!(wma.next(3.0), 2.3333333333, epsilon = 1e-6); approx::assert_relative_eq!(wma.next(4.0), 3.3333333333, epsilon = 1e-6); }
162}
163
164pub const SMA_METADATA: IndicatorMetadata = IndicatorMetadata {
165 name: "Simple Moving Average",
166 description: "The Simple Moving Average calculates the unweighted mean of the previous N data points.",
167 usage: "Use as the foundational smoothing module providing SMA, EMA, WMA, and SMMA implementations that power higher-level indicators across the library.",
168 keywords: &["moving-average", "smoothing", "classic", "ema"],
169 ehlers_summary: "The core smoothing algorithms — SMA, EMA, WMA — are the building blocks of nearly all technical indicators. EMA applies exponential decay weighting (alpha = 2/(n+1)), SMA applies uniform weighting over N bars, and WMA applies linearly increasing weights emphasizing more recent bars.",
170 params: &[ParamDef {
171 name: "period",
172 default: "14",
173 description: "Smoothing period",
174 }],
175 formula_source: "https://www.investopedia.com/terms/s/sma.asp",
176 formula_latex: r#"
177\[
178SMA = \frac{1}{n} \sum_{i=1}^{n} P_i
179\]
180"#,
181 gold_standard_file: "sma.json",
182 category: "Classic",
183};
184
185pub const EMA_METADATA: IndicatorMetadata = IndicatorMetadata {
186 name: "Exponential Moving Average",
187 description: "The Exponential Moving Average gives more weight to recent prices.",
188 usage: "Use as the foundational smoothing module providing SMA, EMA, WMA, and SMMA implementations that power higher-level indicators across the library.",
189 keywords: &["moving-average", "smoothing", "classic", "ema"],
190 ehlers_summary: "The core smoothing algorithms — SMA, EMA, WMA — are the building blocks of nearly all technical indicators. EMA applies exponential decay weighting (alpha = 2/(n+1)), SMA applies uniform weighting over N bars, and WMA applies linearly increasing weights emphasizing more recent bars.",
191 params: &[ParamDef {
192 name: "period",
193 default: "14",
194 description: "Smoothing period",
195 }],
196 formula_source: "https://www.investopedia.com/terms/e/ema.asp",
197 formula_latex: r#"
198\[
199EMA = P_t \times \alpha + EMA_{t-1} \times (1 - \alpha)
200\]
201"#,
202 gold_standard_file: "ema.json",
203 category: "Classic",
204};
205
206pub const WMA_METADATA: IndicatorMetadata = IndicatorMetadata {
207 name: "Weighted Moving Average",
208 description: "The Weighted Moving Average assigns linearly decreasing weights.",
209 usage: "Use as the foundational smoothing module providing SMA, EMA, WMA, and SMMA implementations that power higher-level indicators across the library.",
210 keywords: &["moving-average", "smoothing", "classic", "ema"],
211 ehlers_summary: "The core smoothing algorithms — SMA, EMA, WMA — are the building blocks of nearly all technical indicators. EMA applies exponential decay weighting (alpha = 2/(n+1)), SMA applies uniform weighting over N bars, and WMA applies linearly increasing weights emphasizing more recent bars.",
212 params: &[ParamDef {
213 name: "period",
214 default: "14",
215 description: "Smoothing period",
216 }],
217 formula_source: "https://www.investopedia.com/articles/technical/060401.asp",
218 formula_latex: r#"
219\[
220WMA = \frac{P_1 \times n + P_2 \times (n-1) + \dots}{n + (n-1) + \dots + 1}
221\]
222"#,
223 gold_standard_file: "wma.json",
224 category: "Classic",
225};