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