quantwave_core/indicators/
ehlers_autocorrelation.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::indicators::ultimate_smoother::UltimateSmoother;
3use crate::traits::Next;
4use std::collections::VecDeque;
5
6#[derive(Debug, Clone)]
12pub struct EhlersAutocorrelation {
13 length: usize,
14 num_lags: usize,
15 smoother: UltimateSmoother,
16 filt_history: VecDeque<f64>,
17}
18
19impl EhlersAutocorrelation {
20 pub fn new(length: usize, num_lags: usize) -> Self {
21 Self {
22 length,
23 num_lags,
24 smoother: UltimateSmoother::new(20), filt_history: VecDeque::from(vec![0.0; length + num_lags]),
26 }
27 }
28
29 pub fn with_smoother_period(length: usize, num_lags: usize, smoother_period: usize) -> Self {
30 Self {
31 length,
32 num_lags,
33 smoother: UltimateSmoother::new(smoother_period),
34 filt_history: VecDeque::from(vec![0.0; length + num_lags]),
35 }
36 }
37}
38
39impl Next<f64> for EhlersAutocorrelation {
40 type Output = Vec<f64>; fn next(&mut self, input: f64) -> Self::Output {
43 let filt = self.smoother.next(input);
44 self.filt_history.push_front(filt);
45 self.filt_history.pop_back();
46
47 let mut results = Vec::with_capacity(self.num_lags);
48 let len_f = self.length as f64;
49
50 for lag in 0..self.num_lags {
51 let mut sx = 0.0;
52 let mut sy = 0.0;
53 let mut sxx = 0.0;
54 let mut sxy = 0.0;
55 let mut syy = 0.0;
56
57 for j in 0..self.length {
58 let x = self.filt_history[j];
59 let y = self.filt_history[lag + j];
60 sx += x;
61 sy += y;
62 sxx += x * x;
63 sxy += x * y;
64 syy += y * y;
65 }
66
67 let denom_x = len_f * sxx - sx * sx;
68 let denom_y = len_f * syy - sy * sy;
69
70 let corr = if denom_x > 0.0 && denom_y > 0.0 {
71 (len_f * sxy - sx * sy) / (denom_x * denom_y).sqrt()
72 } else if lag == 0 {
73 1.0
74 } else {
75 0.0
76 };
77
78 results.push(corr);
79 }
80
81 results
82 }
83}
84
85pub const EHLERS_AUTOCORRELATION_METADATA: IndicatorMetadata = IndicatorMetadata {
86 name: "Ehlers Autocorrelation",
87 description: "Computes Pearson correlation of smoothed price with its lags to identify market structure.",
88 params: &[
89 ParamDef {
90 name: "length",
91 default: "20",
92 description: "Correlation window length",
93 },
94 ParamDef {
95 name: "num_lags",
96 default: "100",
97 description: "Number of lags to compute",
98 },
99 ],
100 formula_source: "https://github.com/lavs9/quantwave/blob/main/references/traderstipsreference/TRADERS’ TIPS - FEBRUARY 2025.html",
101 formula_latex: r#"
102\[
103\rho(lag) = \frac{N \sum X Y - \sum X \sum Y}{\sqrt{(N \sum X^2 - (\sum X)^2)(N \sum Y^2 - (\sum Y)^2)}}
104\]
105"#,
106 gold_standard_file: "ehlers_autocorrelation.json",
107 category: "Ehlers DSP",
108};
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::traits::Next;
114 use crate::test_utils::{load_gold_standard_vec, assert_indicator_parity_vec};
115 use proptest::prelude::*;
116
117 #[test]
118 fn test_ehlers_autocorrelation_gold_standard() {
119 let case = load_gold_standard_vec("ehlers_autocorrelation");
120 let ac = EhlersAutocorrelation::new(20, 10);
121 assert_indicator_parity_vec(ac, &case.input, &case.expected);
122 }
123
124 #[test]
125 fn test_ehlers_autocorrelation_basic() {
126 let mut ac = EhlersAutocorrelation::new(20, 10);
127 let inputs = vec![10.0, 11.0, 12.0, 13.0, 14.0, 15.0];
128 for input in inputs {
129 let res = ac.next(input);
130 assert_eq!(res.len(), 10);
131 approx::assert_relative_eq!(res[0], 1.0, epsilon = 1e-10);
132 }
133 }
134
135 proptest! {
136 #[test]
137 fn test_ehlers_autocorrelation_parity(
138 inputs in prop::collection::vec(1.0..100.0, 50..100),
139 ) {
140 let length = 20;
141 let num_lags = 10;
142 let mut ac = EhlersAutocorrelation::new(length, num_lags);
143 let streaming_results: Vec<Vec<f64>> = inputs.iter().map(|&x| ac.next(x)).collect();
144
145 let mut batch_results = Vec::with_capacity(inputs.len());
147 let mut smoother = UltimateSmoother::new(20);
148 let filtered: Vec<f64> = inputs.iter().map(|&x| smoother.next(x)).collect();
149
150 for i in 0..inputs.len() {
151 let mut bar_results = Vec::with_capacity(num_lags);
152 for lag in 0..num_lags {
153 let mut sx = 0.0;
154 let mut sy = 0.0;
155 let mut sxx = 0.0;
156 let mut sxy = 0.0;
157 let mut syy = 0.0;
158
159 for j in 0..length {
160 let idx_x = i as i32 - j as i32;
161 let idx_y = i as i32 - (lag + j) as i32;
162
163 let x = if idx_x >= 0 { filtered[idx_x as usize] } else { 0.0 };
164 let y = if idx_y >= 0 { filtered[idx_y as usize] } else { 0.0 };
165
166 sx += x;
167 sy += y;
168 sxx += x * x;
169 sxy += x * y;
170 syy += y * y;
171 }
172
173 let len_f = length as f64;
174 let denom_x = len_f * sxx - sx * sx;
175 let denom_y = len_f * syy - sy * sy;
176
177 let corr = if denom_x > 0.0 && denom_y > 0.0 {
178 (len_f * sxy - sx * sy) / (denom_x * denom_y).sqrt()
179 } else if lag == 0 {
180 1.0
181 } else {
182 0.0
183 };
184 bar_results.push(corr);
185 }
186 batch_results.push(bar_results);
187 }
188
189 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
190 for (sv, bv) in s.iter().zip(b.iter()) {
191 approx::assert_relative_eq!(sv, bv, epsilon = 1e-10);
192 }
193 }
194 }
195 }
196}