quantwave_core/indicators/
dsma.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3use std::collections::VecDeque;
4use std::f64::consts::PI;
5
6#[derive(Debug, Clone)]
14pub struct DSMA {
15 period: usize,
16 c1: f64,
17 c2: f64,
18 c3: f64,
19 price_history: VecDeque<f64>,
20 zeros_history: [f64; 2],
21 filt_history: [f64; 2],
22 filt_window: VecDeque<f64>,
23 dsma_prev: f64,
24 count: usize,
25}
26
27impl DSMA {
28 pub fn new(period: usize) -> Self {
29 let period_f = period as f64;
30 let a1 = (-1.414 * PI / (0.5 * period_f)).exp();
31 let c2 = 2.0 * a1 * (1.414 * PI / (0.5 * period_f)).cos();
32 let c3 = -a1 * a1;
33 let c1 = 1.0 - c2 - c3;
34
35 Self {
36 period,
37 c1,
38 c2,
39 c3,
40 price_history: VecDeque::from(vec![0.0; 4]),
41 zeros_history: [0.0; 2],
42 filt_history: [0.0; 2],
43 filt_window: VecDeque::from(vec![0.0; period]),
44 dsma_prev: 0.0,
45 count: 0,
46 }
47 }
48}
49
50impl Next<f64> for DSMA {
51 type Output = f64;
52
53 fn next(&mut self, input: f64) -> Self::Output {
54 self.count += 1;
55
56 self.price_history.push_front(input);
58 self.price_history.pop_back();
59
60 if self.count == 1 {
61 self.dsma_prev = input;
62 return input;
63 }
64
65 let zeros = self.price_history[0] - self.price_history[2];
67
68 let filt = self.c1 * (zeros + self.zeros_history[0]) / 2.0
70 + self.c2 * self.filt_history[0]
71 + self.c3 * self.filt_history[1];
72
73 self.zeros_history[1] = self.zeros_history[0];
74 self.zeros_history[0] = zeros;
75
76 self.filt_history[1] = self.filt_history[0];
77 self.filt_history[0] = filt;
78
79 self.filt_window.push_front(filt);
80 self.filt_window.pop_back();
81
82 let mut sum_sq = 0.0;
85 for &f in &self.filt_window {
86 sum_sq += f * f;
87 }
88 let rms = (sum_sq / self.period as f64).sqrt();
89
90 let scaled_filt = if rms != 0.0 { filt / rms } else { 0.0 };
92
93 let mut alpha1 = scaled_filt.abs() * 5.0 / self.period as f64;
95 if alpha1 > 1.0 {
96 alpha1 = 1.0;
97 }
98
99 let dsma = alpha1 * input + (1.0 - alpha1) * self.dsma_prev;
101 self.dsma_prev = dsma;
102
103 dsma
104 }
105}
106
107pub const DSMA_METADATA: IndicatorMetadata = IndicatorMetadata {
108 name: "DSMA",
109 description: "Deviation Scaled Moving Average adapts to price variations using standard deviation scaled oscillators.",
110 params: &[ParamDef {
111 name: "period",
112 default: "40",
113 description: "Critical period for smoothing and RMS calculation",
114 }],
115 formula_source: "https://github.com/lavs9/quantwave/blob/main/references/Ehlers%20Papers/DEVIATION%20SCALED%20MOVING%20AVERAGE.pdf",
116 formula_latex: r#"
117\[
118Zeros = Close - Close_{t-2}
119\]
120\[
121Filt = c_1 \frac{Zeros + Zeros_{t-1}}{2} + c_2 Filt_{t-1} + c_3 Filt_{t-2}
122\]
123\[
124RMS = \sqrt{\frac{1}{P} \sum_{i=0}^{P-1} Filt_{t-i}^2}
125\]
126\[
127\alpha = \min\left(1.0, \left| \frac{Filt}{RMS} \right| \frac{5}{P}\right)
128\]
129\[
130DSMA = \alpha \cdot Close + (1 - \alpha) \cdot DSMA_{t-1}
131\]
132"#,
133 gold_standard_file: "dsma.json",
134 category: "Ehlers DSP",
135};
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use crate::traits::Next;
141 use proptest::prelude::*;
142
143 #[test]
144 fn test_dsma_basic() {
145 let mut dsma = DSMA::new(40);
146 let inputs = vec![10.0, 11.0, 12.0, 13.0, 14.0, 15.0];
147 for input in inputs {
148 let res = dsma.next(input);
149 assert!(!res.is_nan());
150 }
151 }
152
153 proptest! {
154 #[test]
155 fn test_dsma_parity(
156 inputs in prop::collection::vec(1.0..100.0, 100..200),
157 ) {
158 let period = 40;
159 let mut dsma = DSMA::new(period);
160 let streaming_results: Vec<f64> = inputs.iter().map(|&x| dsma.next(x)).collect();
161
162 let mut batch_results = Vec::with_capacity(inputs.len());
164 let period_f = period as f64;
165 let a1 = (-1.414 * PI / (0.5 * period_f)).exp();
166 let c2 = 2.0 * a1 * (1.414 * PI / (0.5 * period_f)).cos();
167 let c3 = -a1 * a1;
168 let c1 = 1.0 - c2 - c3;
169
170 let mut price_hist = vec![0.0; inputs.len() + 4];
171 let mut zeros_hist = vec![0.0; inputs.len() + 4];
172 let mut filt_hist = vec![0.0; inputs.len() + 4];
173 let mut dsma_prev = 0.0;
174
175 for (i, &input) in inputs.iter().enumerate() {
176 let bar = i + 1;
177 let idx = i + 2; price_hist[idx] = input;
179
180 if bar == 1 {
181 dsma_prev = input;
182 batch_results.push(input);
183 continue;
184 }
185
186 let zeros = price_hist[idx] - price_hist[idx-2];
187 zeros_hist[idx] = zeros;
188
189 let filt = c1 * (zeros + zeros_hist[idx-1]) / 2.0
190 + c2 * filt_hist[idx-1]
191 + c3 * filt_hist[idx-2];
192 filt_hist[idx] = filt;
193
194 let mut sum_sq = 0.0;
195 for j in 0..period {
196 if idx >= j {
197 let f = filt_hist[idx-j];
198 sum_sq += f * f;
199 }
200 }
201 let rms = (sum_sq / period_f).sqrt();
202
203 let scaled_filt = if rms != 0.0 { filt / rms } else { 0.0 };
204 let mut alpha1 = scaled_filt.abs() * 5.0 / period_f;
205 if alpha1 > 1.0 { alpha1 = 1.0; }
206
207 let dsma = alpha1 * input + (1.0 - alpha1) * dsma_prev;
208 dsma_prev = dsma;
209 batch_results.push(dsma);
210 }
211
212 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
213 approx::assert_relative_eq!(s, b, epsilon = 1e-10);
214 }
215 }
216 }
217}