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