Skip to main content

quantwave_core/indicators/
fourier_series.rs

1use crate::indicators::metadata::{IndicatorMetadata, ParamDef};
2use crate::traits::Next;
3use crate::indicators::bandpass::BandPass;
4use crate::indicators::smoothing::SMA;
5use std::f64::consts::PI;
6
7/// Fourier Series Model
8///
9/// Based on John Ehlers' "Fourier Series Model of the Market".
10/// Synthesizes a smoothed market waveform by combining the fundamental,
11/// second, and third harmonics of a base cycle, weighted by their
12/// relative power.
13#[derive(Debug, Clone)]
14pub struct FourierSeriesModel {
15    fundamental: usize,
16    bp1: BandPass,
17    bp2: BandPass,
18    bp3: BandPass,
19    bp1_prev: f64,
20    bp2_prev: f64,
21    bp3_prev: f64,
22    p1_sma: SMA,
23    p2_sma: SMA,
24    p3_sma: SMA,
25    count: usize,
26}
27
28impl FourierSeriesModel {
29    pub fn new(fundamental: usize) -> Self {
30        Self {
31            fundamental,
32            bp1: BandPass::new(fundamental, 0.1),
33            bp2: BandPass::new(fundamental / 2, 0.1),
34            bp3: BandPass::new(fundamental / 3, 0.1),
35            bp1_prev: 0.0,
36            bp2_prev: 0.0,
37            bp3_prev: 0.0,
38            p1_sma: SMA::new(fundamental),
39            p2_sma: SMA::new(fundamental),
40            p3_sma: SMA::new(fundamental),
41            count: 0,
42        }
43    }
44}
45
46impl Default for FourierSeriesModel {
47    fn default() -> Self {
48        Self::new(20)
49    }
50}
51
52impl Next<f64> for FourierSeriesModel {
53    type Output = f64;
54
55    fn next(&mut self, input: f64) -> Self::Output {
56        self.count += 1;
57        
58        let bp1 = self.bp1.next(input);
59        let bp2 = self.bp2.next(input);
60        let bp3 = self.bp3.next(input);
61
62        // Quadrature components (approximate derivatives)
63        // Q = (Fundamental / 6.28) * (BP - BP[1])
64        let q_scale = self.fundamental as f64 / (2.0 * PI);
65        let q1 = q_scale * (bp1 - self.bp1_prev);
66        let q2 = q_scale * (bp2 - self.bp2_prev);
67        let q3 = q_scale * (bp3 - self.bp3_prev);
68
69        // Power components (summed over fundamental period)
70        let p1 = self.p1_sma.next(bp1 * bp1 + q1 * q1) * self.fundamental as f64;
71        let p2 = self.p2_sma.next(bp2 * bp2 + q2 * q2) * self.fundamental as f64;
72        let p3 = self.p3_sma.next(bp3 * bp3 + q3 * q3) * self.fundamental as f64;
73
74        // Shift history
75        self.bp1_prev = bp1;
76        self.bp2_prev = bp2;
77        self.bp3_prev = bp3;
78
79        // Synthesized wave
80        // Wave = BP1 + sqrt(P2/P1)*BP2 + sqrt(P3/P1)*BP3
81        let mut wave = bp1;
82        if p1 > 0.0 {
83            wave += (p2 / p1).sqrt() * bp2;
84            wave += (p3 / p1).sqrt() * bp3;
85        }
86
87        wave
88    }
89}
90
91pub const FOURIER_SERIES_MODEL_METADATA: IndicatorMetadata = IndicatorMetadata {
92    name: "FourierSeriesModel",
93    description: "Synthesized market model using fundamental and harmonic frequency components.",
94    params: &[
95        ParamDef {
96            name: "fundamental",
97            default: "20",
98            description: "Fundamental cycle period",
99        },
100    ],
101    formula_source: "https://github.com/lavs9/quantwave/blob/main/references/Ehlers%20Papers/FOURIER%20SERIES%20MODEL%20OF%20THE%20MARKET.pdf",
102    formula_latex: r#"
103\[
104BP_k = \text{BandPass}(Price, Fundamental/k)
105\]
106\[
107Q_k = \frac{Fundamental}{2\pi} (BP_{k} - BP_{k,t-1})
108\]
109\[
110P_k = \sum_{n=0}^{F-1} (BP_{k,t-n}^2 + Q_{k,t-n}^2)
111\]
112\[
113Wave = BP_1 + \sqrt{P_2/P_1}BP_2 + \sqrt{P_3/P_1}BP_3
114\]
115"#,
116    gold_standard_file: "fourier_series_model.json",
117    category: "Ehlers DSP",
118};
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use crate::traits::Next;
124    use proptest::prelude::*;
125
126    #[test]
127    fn test_fourier_series_basic() {
128        let mut fsm = FourierSeriesModel::new(20);
129        for i in 0..100 {
130            let val = fsm.next(100.0 + (i as f64 * 0.1).sin());
131            assert!(!val.is_nan());
132        }
133    }
134
135    proptest! {
136        #[test]
137        fn test_fourier_series_parity(
138            inputs in prop::collection::vec(1.0..100.0, 100..150),
139        ) {
140            let fundamental = 20;
141            let mut fsm = FourierSeriesModel::new(fundamental);
142            let streaming_results: Vec<f64> = inputs.iter().map(|&x| fsm.next(x)).collect();
143
144            // Batch implementation
145            let mut batch_results = Vec::with_capacity(inputs.len());
146            let mut bp1_obj = BandPass::new(fundamental, 0.1);
147            let mut bp2_obj = BandPass::new(fundamental / 2, 0.1);
148            let mut bp3_obj = BandPass::new(fundamental / 3, 0.1);
149            
150            let mut bp1_vals = Vec::new();
151            let mut bp2_vals = Vec::new();
152            let mut bp3_vals = Vec::new();
153            let mut q1_vals = Vec::new();
154            let mut q2_vals = Vec::new();
155            let mut q3_vals = Vec::new();
156
157            let q_scale = fundamental as f64 / (2.0 * PI);
158
159            for (i, &input) in inputs.iter().enumerate() {
160                let bp1 = bp1_obj.next(input);
161                let bp2 = bp2_obj.next(input);
162                let bp3 = bp3_obj.next(input);
163
164                let q1 = q_scale * (bp1 - (if i > 0 { bp1_vals[i-1] } else { 0.0 }));
165                let q2 = q_scale * (bp2 - (if i > 0 { bp2_vals[i-1] } else { 0.0 }));
166                let q3 = q_scale * (bp3 - (if i > 0 { bp3_vals[i-1] } else { 0.0 }));
167
168                bp1_vals.push(bp1);
169                bp2_vals.push(bp2);
170                bp3_vals.push(bp3);
171                q1_vals.push(q1);
172                q2_vals.push(q2);
173                q3_vals.push(q3);
174
175                let mut p1 = 0.0;
176                let mut p2 = 0.0;
177                let mut p3 = 0.0;
178                let start = if i >= fundamental - 1 { i + 1 - fundamental } else { 0 };
179                let _count = i + 1 - start;
180                for j in start..=i {
181                    p1 += bp1_vals[j] * bp1_vals[j] + q1_vals[j] * q1_vals[j];
182                    p2 += bp2_vals[j] * bp2_vals[j] + q2_vals[j] * q2_vals[j];
183                    p3 += bp3_vals[j] * bp3_vals[j] + q3_vals[j] * q3_vals[j];
184                }
185                
186                // Normalizing to moving average-like behavior
187                // (Wait, SMA * period is just the sum)
188                
189                let mut wave = bp1;
190                if p1 > 0.0 {
191                    wave += (p2 / p1).sqrt() * bp2;
192                    wave += (p3 / p1).sqrt() * bp3;
193                }
194                batch_results.push(wave);
195            }
196
197            for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
198                approx::assert_relative_eq!(s, b, epsilon = 1e-10);
199            }
200        }
201    }
202}