quantwave_core/indicators/
griffiths_predictor.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3use crate::indicators::high_pass::HighPass;
4use crate::indicators::super_smoother::SuperSmoother;
5use std::collections::VecDeque;
6
7#[derive(Debug, Clone)]
12pub struct GriffithsPredictor {
13 length: usize,
14 bars_fwd: usize,
15 mu: f64,
16 hp: HighPass,
17 ss: SuperSmoother,
18 peak: f64,
19 signal_window: VecDeque<f64>,
20 coef: Vec<f64>,
21}
22
23impl GriffithsPredictor {
24 pub fn new(lower_bound: usize, upper_bound: usize, length: usize, bars_fwd: usize) -> Self {
25 Self {
26 length,
27 bars_fwd,
28 mu: 1.0 / (length as f64),
29 hp: HighPass::new(upper_bound),
30 ss: SuperSmoother::new(lower_bound),
31 peak: 0.1,
32 signal_window: VecDeque::with_capacity(length + 1),
33 coef: vec![0.0; length + 1], }
35 }
36}
37
38impl Default for GriffithsPredictor {
39 fn default() -> Self {
40 Self::new(18, 40, 18, 2)
41 }
42}
43
44impl Next<f64> for GriffithsPredictor {
45 type Output = f64;
46
47 fn next(&mut self, input: f64) -> Self::Output {
48 let hp_val = self.hp.next(input);
49 let lp_val = self.ss.next(hp_val);
50
51 self.peak *= 0.991;
53 if lp_val.abs() > self.peak {
54 self.peak = lp_val.abs();
55 }
56
57 let signal = if self.peak != 0.0 {
58 lp_val / self.peak
59 } else {
60 0.0
61 };
62
63 self.signal_window.push_front(signal);
64 if self.signal_window.len() > self.length {
65 self.signal_window.pop_back();
66 }
67
68 if self.signal_window.len() < self.length {
69 return 0.0;
70 }
71
72 let mut xx = vec![0.0; self.length + 1];
81 for (i, val) in xx.iter_mut().enumerate().skip(1).take(self.length) {
82 *val = self.signal_window[self.length - i];
83 }
84
85 let mut x_bar = 0.0;
86 for count in 1..=self.length {
87 x_bar += xx[self.length - count] * self.coef[count];
88 }
89
90 for count in 1..=self.length {
91 self.coef[count] += self.mu * (xx[self.length] - x_bar) * xx[self.length - count];
92 }
93
94 let mut x_pred = 0.0;
96 let mut xx_temp = xx.clone();
97 for _advance in 1..=self.bars_fwd {
98 x_pred = 0.0;
99 for count in 1..=self.length {
100 x_pred += xx_temp[self.length + 1 - count] * self.coef[count];
101 }
102
103 for count in 1..self.length {
105 xx_temp[count] = xx_temp[count + 1];
106 }
107 xx_temp[self.length] = x_pred;
108 }
109
110 x_pred
111 }
112}
113
114pub const GRIFFITHS_PREDICTOR_METADATA: IndicatorMetadata = IndicatorMetadata {
115 name: "GriffithsPredictor",
116 description: "Adaptive LMS linear predictive filter for signal forecasting.",
117 usage: "Use for short-horizon price prediction by projecting the dominant market cycle one or two bars forward. Works best in oscillating markets; disable in strong trends.",
118 keywords: &["prediction", "cycle", "ehlers", "dsp"],
119 ehlers_summary: "The Griffiths Predictor uses autoregressive coefficients from the Griffiths cycle measurement to extrapolate the current dominant cycle one bar ahead. By fitting an AR model to cycle-filtered price, it generates a one-step-ahead forecast useful for anticipatory entries at predicted cycle turns.",
120 params: &[
121 ParamDef {
122 name: "lower_bound",
123 default: "18",
124 description: "Lower frequency bound (SS length)",
125 },
126 ParamDef {
127 name: "upper_bound",
128 default: "40",
129 description: "Upper frequency bound (HP length)",
130 },
131 ParamDef {
132 name: "length",
133 default: "18",
134 description: "LMS filter length",
135 },
136 ParamDef {
137 name: "bars_fwd",
138 default: "2",
139 description: "Number of bars to predict forward",
140 },
141 ],
142 formula_source: "https://github.com/lavs9/quantwave/blob/main/references/traderstipsreference/TRADERS’%20TIPS%20-%20JANUARY%202025.html",
143 formula_latex: r#"
144\[
145\mu = 1/L
146\]
147\[
148\bar{x} = \sum_{i=1}^L xx_{L-i} \cdot coef_i
149\]
150\[
151coef_i = coef_i + \mu(xx_L - \bar{x})xx_{L-i}
152\]
153"#,
154 gold_standard_file: "griffiths_predictor.json",
155 category: "Ehlers DSP",
156};
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::traits::Next;
162 use proptest::prelude::*;
163
164 #[test]
165 fn test_griffiths_predictor_basic() {
166 let mut gp = GriffithsPredictor::new(18, 40, 18, 2);
167 for i in 0..100 {
168 let val = gp.next(100.0 + (i as f64 * 0.1).sin());
169 assert!(!val.is_nan());
170 }
171 }
172
173 proptest! {
174 #[test]
175 fn test_griffiths_predictor_parity(
176 inputs in prop::collection::vec(1.0..100.0, 100..200),
177 ) {
178 let lb = 18;
179 let ub = 40;
180 let length = 18;
181 let bars_fwd = 2;
182 let mut gp = GriffithsPredictor::new(lb, ub, length, bars_fwd);
183 let streaming_results: Vec<f64> = inputs.iter().map(|&x| gp.next(x)).collect();
184
185 let mut batch_results = Vec::with_capacity(inputs.len());
187 let mut hp = HighPass::new(ub);
188 let mut ss = SuperSmoother::new(lb);
189 let lp_vals: Vec<f64> = inputs.iter().map(|&x| ss.next(hp.next(x))).collect();
190
191 let mut peak = 0.1;
192 let mut signals = Vec::new();
193 let mut coef = vec![0.0; length + 1];
194 let mu = 1.0 / length as f64;
195
196 for (i, &lp_val) in lp_vals.iter().enumerate() {
197 peak *= 0.991;
198 if lp_val.abs() > peak {
199 peak = lp_val.abs();
200 }
201 let signal = if peak != 0.0 { lp_val / peak } else { 0.0 };
202 signals.push(signal);
203
204 if signals.len() < length {
205 batch_results.push(0.0);
206 continue;
207 }
208
209 let mut xx = vec![0.0; length + 1];
210 for j in 1..=length {
211 xx[j] = signals[i - (length - j)];
212 }
213
214 let mut x_bar = 0.0;
215 for count in 1..=length {
216 x_bar += xx[length - count] * coef[count];
217 }
218
219 for count in 1..=length {
220 coef[count] += mu * (xx[length] - x_bar) * xx[length - count];
221 }
222
223 let mut x_pred = 0.0;
224 let mut xx_temp = xx.clone();
225 for _advance in 1..=bars_fwd {
226 x_pred = 0.0;
227 for count in 1..=length {
228 x_pred += xx_temp[length + 1 - count] * coef[count];
229 }
230 for count in 1..length {
231 xx_temp[count] = xx_temp[count + 1];
232 }
233 xx_temp[length] = x_pred;
234 }
235 batch_results.push(x_pred);
236 }
237
238 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
239 approx::assert_relative_eq!(s, b, epsilon = 1e-10);
240 }
241 }
242 }
243}