wickra_core/indicators/
sine_wave.rs1#![allow(clippy::manual_clamp)]
3
4use std::f64::consts::PI;
5
6use crate::indicators::hilbert_dominant_cycle::HilbertDominantCycle;
7use crate::traits::Indicator;
8
9#[derive(Debug, Clone, Default)]
34pub struct SineWave {
35 cycle: HilbertDominantCycle,
36 smooth_buf: Vec<f64>,
37 detrender_buf: Vec<f64>,
38 last_phase: f64,
39 last_sine: Option<f64>,
40 last_lead: f64,
41 count: usize,
42}
43
44impl SineWave {
45 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub const fn lead(&self) -> f64 {
52 self.last_lead
53 }
54
55 pub const fn value(&self) -> Option<f64> {
57 self.last_sine
58 }
59
60 fn push_front(buf: &mut Vec<f64>, v: f64, cap: usize) {
61 buf.insert(0, v);
62 if buf.len() > cap {
63 buf.truncate(cap);
64 }
65 }
66}
67
68impl Indicator for SineWave {
69 type Input = f64;
70 type Output = f64;
71
72 fn update(&mut self, input: f64) -> Option<f64> {
73 if !input.is_finite() {
74 return None;
75 }
76 self.count += 1;
77 let _ = self.cycle.update(input);
80
81 Self::push_front(&mut self.smooth_buf, input, 7);
82 if self.smooth_buf.len() < 4 {
83 return None;
84 }
85 let smooth = (4.0 * self.smooth_buf[0]
86 + 3.0 * self.smooth_buf[1]
87 + 2.0 * self.smooth_buf[2]
88 + self.smooth_buf[3])
89 / 10.0;
90 if self.smooth_buf.len() < 7 {
91 return None;
92 }
93 let period = self.cycle.value().unwrap_or(15.0).max(6.0).min(50.0);
94 let adj = 0.075 * period + 0.54;
95 let s0 = smooth;
96 let s2 = self.smooth_buf[2];
97 let s4 = self.smooth_buf[4];
98 let s6 = self.smooth_buf[6];
99 let detrender = (0.0962 * s0 + 0.5769 * s2 - 0.5769 * s4 - 0.0962 * s6) * adj;
100 Self::push_front(&mut self.detrender_buf, detrender, 7);
101 if self.detrender_buf.len() < 7 {
102 return None;
103 }
104 let q1 = (0.0962 * self.detrender_buf[0] + 0.5769 * self.detrender_buf[2]
105 - 0.5769 * self.detrender_buf[4]
106 - 0.0962 * self.detrender_buf[6])
107 * adj;
108 let i1 = self.detrender_buf[3];
109 let phase = if i1.abs() > f64::EPSILON {
110 (q1 / i1).atan()
111 } else {
112 self.last_phase
113 };
114 self.last_phase = phase;
115 let sine = phase.sin();
116 let lead = (phase + PI / 4.0).sin();
117
118 if self.count < 50 {
119 return None;
120 }
121 self.last_sine = Some(sine);
122 self.last_lead = lead;
123 Some(sine)
124 }
125
126 fn reset(&mut self) {
127 self.cycle.reset();
128 self.smooth_buf.clear();
129 self.detrender_buf.clear();
130 self.last_phase = 0.0;
131 self.last_sine = None;
132 self.last_lead = 0.0;
133 self.count = 0;
134 }
135
136 #[inline]
137 fn warmup_period(&self) -> usize {
138 50
139 }
140
141 #[inline]
142 fn is_ready(&self) -> bool {
143 self.last_sine.is_some()
144 }
145
146 #[inline]
147 fn name(&self) -> &'static str {
148 "SineWave"
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155 use crate::traits::BatchExt;
156
157 #[test]
158 fn accessors_and_metadata() {
159 let mut sw = SineWave::new();
160 assert_eq!(sw.warmup_period(), 50);
161 assert_eq!(sw.name(), "SineWave");
162 assert!(!sw.is_ready());
163 assert!(sw.value().is_none());
164 let prices: Vec<f64> = (0..120)
165 .map(|i| 100.0 + (f64::from(i) * 0.4).sin() * 5.0)
166 .collect();
167 sw.batch(&prices);
168 assert!(sw.is_ready());
169 assert!(sw.value().is_some());
170 }
171
172 #[test]
173 fn output_bounded() {
174 let prices: Vec<f64> = (0..200)
175 .map(|i| 100.0 + (f64::from(i) * 0.3).cos() * 5.0)
176 .collect();
177 let mut sw = SineWave::new();
178 for v in sw.batch(&prices).into_iter().flatten() {
179 assert!((-1.0..=1.0).contains(&v), "sine out of bounds: {v}");
180 }
181 assert!(sw.lead() >= -1.0 && sw.lead() <= 1.0);
183 }
184
185 #[test]
186 fn batch_equals_streaming() {
187 let prices: Vec<f64> = (0..200)
188 .map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0)
189 .collect();
190 let mut a = SineWave::new();
191 let mut b = SineWave::new();
192 let batch = a.batch(&prices);
193 let streamed: Vec<_> = prices.iter().map(|p| b.update(*p)).collect();
194 assert_eq!(batch, streamed);
195 }
196
197 #[test]
198 fn ignores_non_finite_input() {
199 let mut sw = SineWave::new();
200 let prices: Vec<f64> = (0..120)
201 .map(|i| 100.0 + (f64::from(i) * 0.4).sin() * 5.0)
202 .collect();
203 sw.batch(&prices);
204 let before = sw.value();
205 assert!(before.is_some());
206 assert_eq!(sw.update(f64::NAN), None);
207 }
208
209 #[test]
210 fn reset_clears_state() {
211 let mut sw = SineWave::new();
212 let prices: Vec<f64> = (0..120)
213 .map(|i| 100.0 + (f64::from(i) * 0.4).sin() * 5.0)
214 .collect();
215 sw.batch(&prices);
216 assert!(sw.is_ready());
217 sw.reset();
218 assert!(!sw.is_ready());
219 assert!(sw.value().is_none());
220 }
221
222 #[test]
223 fn flat_input_uses_phase_fallback() {
224 let mut sw = SineWave::new();
230 let _ = sw.batch(&[0.0_f64; 120]);
231 assert!(sw.value().is_some());
232 }
233}