quantwave_core/indicators/
fourier_series.rs1use 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#[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 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 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 self.bp1_prev = bp1;
76 self.bp2_prev = bp2;
77 self.bp3_prev = bp3;
78
79 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 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 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}