quantwave_core/indicators/
classic_laguerre.rs1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3
4#[derive(Debug, Clone)]
10pub struct ClassicLaguerre {
11 gamma: f64,
12 l0: f64,
13 l1: f64,
14 l2: f64,
15 l3: f64,
16 count: usize,
17}
18
19impl ClassicLaguerre {
20 pub fn new(gamma: f64) -> Self {
21 Self {
22 gamma,
23 l0: 0.0,
24 l1: 0.0,
25 l2: 0.0,
26 l3: 0.0,
27 count: 0,
28 }
29 }
30}
31
32impl Default for ClassicLaguerre {
33 fn default() -> Self {
34 Self::new(0.8)
35 }
36}
37
38impl Next<f64> for ClassicLaguerre {
39 type Output = f64;
40
41 fn next(&mut self, input: f64) -> Self::Output {
42 self.count += 1;
43
44 if self.count == 1 {
45 self.l0 = input;
46 self.l1 = input;
47 self.l2 = input;
48 self.l3 = input;
49 return input;
50 }
51
52 let prev_l0 = self.l0;
53 let prev_l1 = self.l1;
54 let prev_l2 = self.l2;
55 let prev_l3 = self.l3;
56
57 self.l0 = (1.0 - self.gamma) * input + self.gamma * prev_l0;
58 self.l1 = -self.gamma * self.l0 + prev_l0 + self.gamma * prev_l1;
59 self.l2 = -self.gamma * self.l1 + prev_l1 + self.gamma * prev_l2;
60 self.l3 = -self.gamma * self.l2 + prev_l2 + self.gamma * prev_l3;
61
62 (self.l0 + 2.0 * self.l1 + 2.0 * self.l2 + self.l3) / 6.0
63 }
64}
65
66pub const CLASSIC_LAGUERRE_METADATA: IndicatorMetadata = IndicatorMetadata {
67 name: "Classic Laguerre Filter",
68 description: "The original Laguerre filter from John Ehlers' 2002 'Time Warp' paper.",
69 usage: "Use when a smooth trend estimate with controllable lag using only 4 state variables is needed. Preferred over long EMAs when computational memory is constrained.",
70 keywords: &["filter", "ehlers", "dsp", "smoothing", "laguerre"],
71 ehlers_summary: "The Classic Laguerre Filter uses four first-order IIR sections sharing the same gamma coefficient. In Cybernetic Analysis (2004) Ehlers shows gamma maps directly to an effective period, making it highly tunable with minimal computation.",
72 params: &[ParamDef {
73 name: "gamma",
74 default: "0.8",
75 description: "Smoothing factor (0.0 to 1.0)",
76 }],
77 formula_source: "https://github.com/lavs9/quantwave/blob/main/references/Ehlers%20Papers/TimeWarp.pdf",
78 formula_latex: r#"
79\[
80L_0 = (1 - \gamma) \cdot Price + \gamma \cdot L_{0,t-1}
81\]
82\[
83L_1 = -\gamma L_0 + L_{0,t-1} + \gamma L_{1,t-1}
84\]
85\[
86L_2 = -\gamma L_1 + L_{1,t-1} + \gamma L_{2,t-1}
87\]
88\[
89L_3 = -\gamma L_2 + L_{2,t-1} + \gamma L_{3,t-1}
90\]
91\[
92Filt = \frac{L_0 + 2L_1 + 2L_2 + L_3}{6}
93\]
94"#,
95 gold_standard_file: "classic_laguerre.json",
96 category: "Ehlers DSP",
97};
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use crate::traits::Next;
103 use proptest::prelude::*;
104
105 #[test]
106 fn test_classic_laguerre_basic() {
107 let mut cl = ClassicLaguerre::new(0.8);
108 let inputs = vec![10.0, 11.0, 12.0, 13.0, 14.0];
109 for input in inputs {
110 let res = cl.next(input);
111 assert!(!res.is_nan());
112 }
113 }
114
115 proptest! {
116 #[test]
117 fn test_classic_laguerre_parity(
118 inputs in prop::collection::vec(1.0..100.0, 10..100),
119 ) {
120 let gamma = 0.8;
121 let mut cl = ClassicLaguerre::new(gamma);
122 let streaming_results: Vec<f64> = inputs.iter().map(|&x| cl.next(x)).collect();
123
124 let mut batch_results = Vec::with_capacity(inputs.len());
125 let mut l0 = 0.0;
126 let mut l1 = 0.0;
127 let mut l2 = 0.0;
128 let mut l3 = 0.0;
129
130 for (i, &input) in inputs.iter().enumerate() {
131 if i == 0 {
132 l0 = input; l1 = input; l2 = input; l3 = input;
133 batch_results.push(input);
134 } else {
135 let prev_l0 = l0;
136 let prev_l1 = l1;
137 let prev_l2 = l2;
138 let prev_l3 = l3;
139
140 l0 = (1.0 - gamma) * input + gamma * prev_l0;
141 l1 = -gamma * l0 + prev_l0 + gamma * prev_l1;
142 l2 = -gamma * l1 + prev_l1 + gamma * prev_l2;
143 l3 = -gamma * l2 + prev_l2 + gamma * prev_l3;
144
145 let res = (l0 + 2.0 * l1 + 2.0 * l2 + l3) / 6.0;
146 batch_results.push(res);
147 }
148 }
149
150 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
151 approx::assert_relative_eq!(s, b, epsilon = 1e-10);
152 }
153 }
154 }
155}