quantwave_core/indicators/
sine_wave.rs1use crate::indicators::metadata::IndicatorMetadata;
2use crate::traits::Next;
3use std::collections::VecDeque;
4
5#[derive(Debug, Clone)]
11pub struct SineWave {
12 price_history: VecDeque<f64>,
13 smooth_history: VecDeque<f64>,
14 detrender_history: VecDeque<f64>,
15 i1_history: VecDeque<f64>,
16 q1_history: VecDeque<f64>,
17 period_prev: f64,
18 count: usize,
19}
20
21impl SineWave {
22 pub fn new() -> Self {
23 Self {
24 price_history: VecDeque::from(vec![0.0; 4]),
25 smooth_history: VecDeque::from(vec![0.0; 7]),
26 detrender_history: VecDeque::from(vec![0.0; 7]),
27 i1_history: VecDeque::from(vec![0.0; 7]),
28 q1_history: VecDeque::from(vec![0.0; 7]),
29 period_prev: 6.0,
30 count: 0,
31 }
32 }
33}
34
35impl Default for SineWave {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41impl Next<f64> for SineWave {
42 type Output = (f64, f64);
43
44 fn next(&mut self, price: f64) -> Self::Output {
45 self.count += 1;
46
47 self.price_history.pop_back();
48 self.price_history.push_front(price);
49
50 if self.count < 7 {
51 return (0.0, 0.0);
52 }
53
54 let smooth = (4.0 * self.price_history[0]
56 + 3.0 * self.price_history[1]
57 + 2.0 * self.price_history[2]
58 + self.price_history[3])
59 / 10.0;
60
61 self.smooth_history.pop_back();
62 self.smooth_history.push_front(smooth);
63
64 let detrender = (0.0962 * self.smooth_history[0] + 0.5769 * self.smooth_history[2]
66 - 0.5769 * self.smooth_history[4]
67 - 0.0962 * self.smooth_history[6])
68 * (0.075 * self.period_prev + 0.54);
69
70 self.detrender_history.pop_back();
71 self.detrender_history.push_front(detrender);
72
73 let q1 = (0.0962 * self.detrender_history[0] + 0.5769 * self.detrender_history[2]
75 - 0.5769 * self.detrender_history[4]
76 - 0.0962 * self.detrender_history[6])
77 * (0.075 * self.period_prev + 0.54);
78
79 let i1 = self.detrender_history[3];
81
82 self.i1_history.pop_back();
83 self.i1_history.push_front(i1);
84 self.q1_history.pop_back();
85 self.q1_history.push_front(q1);
86
87 let mut phase = 0.0;
89 if i1.abs() > 0.0001 {
90 phase = (q1 / i1).atan().to_degrees();
91 }
92
93 let sine = phase.to_radians().sin();
94 let lead_sine = (phase + 45.0).to_radians().sin();
95
96 (sine, lead_sine)
97 }
98}
99
100pub const SINE_WAVE_METADATA: IndicatorMetadata = IndicatorMetadata {
101 name: "Sine Wave",
102 description: "Plots a sine wave and a lead-sine wave based on the cyclic phase of price movement.",
103 params: &[],
104 formula_source: "https://github.com/lavs9/quantwave/blob/main/references/Ehlers%20Papers/ROCKET%20SCIENCE%20FOR%20TRADER.pdf",
105 formula_latex: r#"
106\[
107\text{Sine} = \sin(\text{Phase})
108\]
109\[
110\text{LeadSine} = \sin(\text{Phase} + 45^\circ)
111\]
112"#,
113 gold_standard_file: "sine_wave.json",
114 category: "Rocket Science",
115};
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120 use crate::traits::Next;
121 use proptest::prelude::*;
122
123 #[test]
124 fn test_sine_wave_basic() {
125 let mut sw = SineWave::new();
126 let prices = vec![10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0];
127 for p in prices {
128 let (s, l) = sw.next(p);
129 assert!(!s.is_nan());
130 assert!(!l.is_nan());
131 }
132 }
133
134 proptest! {
135 #[test]
136 fn test_sine_wave_parity(
137 inputs in prop::collection::vec(1.0..100.0, 50..100),
138 ) {
139 let mut sw = SineWave::new();
140 let streaming_results: Vec<(f64, f64)> = inputs.iter().map(|&x| sw.next(x)).collect();
141
142 let mut sw_batch = SineWave::new();
143 let batch_results: Vec<(f64, f64)> = inputs.iter().map(|&x| sw_batch.next(x)).collect();
144
145 for (s, b) in streaming_results.iter().zip(batch_results.iter()) {
146 approx::assert_relative_eq!(s.0, b.0, epsilon = 1e-10);
147 approx::assert_relative_eq!(s.1, b.1, epsilon = 1e-10);
148 }
149 }
150 }
151}