wickra_core/indicators/
sine_wave.rs1use crate::indicators::ht_dcphase::HtDcPhase;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
39pub struct SineWave {
40 phase: HtDcPhase,
41 last_sine: Option<f64>,
42 last_lead: f64,
43}
44
45impl SineWave {
46 pub fn new() -> Self {
48 Self::default()
49 }
50
51 pub const fn lead(&self) -> f64 {
53 self.last_lead
54 }
55
56 pub const fn value(&self) -> Option<f64> {
58 self.last_sine
59 }
60}
61
62impl Indicator for SineWave {
63 type Input = f64;
64 type Output = f64;
65
66 fn update(&mut self, input: f64) -> Option<f64> {
67 if !input.is_finite() {
68 return None;
69 }
70 let phase = self.phase.update(input)?.to_radians();
71 let sine = phase.sin();
72 self.last_lead = (phase + 45f64.to_radians()).sin();
73 self.last_sine = Some(sine);
74 Some(sine)
75 }
76
77 fn reset(&mut self) {
78 self.phase.reset();
79 self.last_sine = None;
80 self.last_lead = 0.0;
81 }
82
83 #[inline]
84 fn warmup_period(&self) -> usize {
85 self.phase.warmup_period()
86 }
87
88 #[inline]
89 fn is_ready(&self) -> bool {
90 self.last_sine.is_some()
91 }
92
93 #[inline]
94 fn name(&self) -> &'static str {
95 "SineWave"
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use crate::traits::BatchExt;
103
104 #[test]
105 fn accessors_and_metadata() {
106 let mut sw = SineWave::new();
107 assert_eq!(sw.warmup_period(), 50);
108 assert_eq!(sw.name(), "SineWave");
109 assert!(!sw.is_ready());
110 assert!(sw.value().is_none());
111 let prices: Vec<f64> = (0..120)
112 .map(|i| 100.0 + (f64::from(i) * 0.4).sin() * 5.0)
113 .collect();
114 sw.batch(&prices);
115 assert!(sw.is_ready());
116 assert!(sw.value().is_some());
117 }
118
119 #[test]
120 fn output_bounded() {
121 let prices: Vec<f64> = (0..200)
122 .map(|i| 100.0 + (f64::from(i) * 0.3).cos() * 5.0)
123 .collect();
124 let mut sw = SineWave::new();
125 for v in sw.batch(&prices).into_iter().flatten() {
126 assert!((-1.0..=1.0).contains(&v), "sine out of bounds: {v}");
127 }
128 assert!(sw.lead() >= -1.0 && sw.lead() <= 1.0);
130 }
131
132 #[test]
133 fn batch_equals_streaming() {
134 let prices: Vec<f64> = (0..200)
135 .map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0)
136 .collect();
137 let mut a = SineWave::new();
138 let mut b = SineWave::new();
139 let batch = a.batch(&prices);
140 let streamed: Vec<_> = prices.iter().map(|p| b.update(*p)).collect();
141 assert_eq!(batch, streamed);
142 }
143
144 #[test]
145 fn ignores_non_finite_input() {
146 let mut sw = SineWave::new();
147 let prices: Vec<f64> = (0..120)
148 .map(|i| 100.0 + (f64::from(i) * 0.4).sin() * 5.0)
149 .collect();
150 sw.batch(&prices);
151 let before = sw.value();
152 assert!(before.is_some());
153 assert_eq!(sw.update(f64::NAN), None);
154 }
155
156 #[test]
157 fn reset_clears_state() {
158 let mut sw = SineWave::new();
159 let prices: Vec<f64> = (0..120)
160 .map(|i| 100.0 + (f64::from(i) * 0.4).sin() * 5.0)
161 .collect();
162 sw.batch(&prices);
163 assert!(sw.is_ready());
164 sw.reset();
165 assert!(!sw.is_ready());
166 assert!(sw.value().is_none());
167 }
168
169 #[test]
170 fn flat_input_uses_phase_fallback() {
171 let mut sw = SineWave::new();
175 let _ = sw.batch(&[0.0_f64; 120]);
176 assert!(sw.value().is_some());
177 }
178
179 use crate::traits::BatchNanExt;
180 use approx::assert_relative_eq;
181
182 fn sine_prices(n: u32) -> Vec<f64> {
183 (0..n)
184 .map(|i| 100.0 + (f64::from(i) * 0.4).sin() * 5.0)
185 .collect()
186 }
187
188 #[test]
189 fn first_value_lands_exactly_at_warmup() {
190 let mut sw = SineWave::new();
191 let out = sw.batch(&sine_prices(120));
192 let warmup = sw.warmup_period();
193 assert!(out[..warmup - 1].iter().all(Option::is_none));
194 assert!(out[warmup - 1].is_some());
195 }
196
197 #[test]
198 fn reset_replays_identically() {
199 let prices = sine_prices(150);
200 let fresh = SineWave::new().batch(&prices);
201 let mut sw = SineWave::new();
202 let first = sw.batch(&prices);
203 sw.reset();
204 assert_eq!(sw.lead().to_bits(), 0.0_f64.to_bits());
205 let second = sw.batch(&prices);
206 assert_eq!(first, fresh);
207 assert_eq!(second, fresh);
208 }
209
210 #[test]
211 fn batch_nan_paths_match_streaming_bitwise() {
212 let prices = sine_prices(150);
213 let mut out = vec![0.0; prices.len()];
214 SineWave::new().batch_nan_into(&prices, &mut out);
215 let nan = SineWave::new().batch_nan(&prices);
216 let fast = SineWave::new().batch_fast(&prices);
217 let mut stream = SineWave::new();
218 let expected: Vec<u64> = prices
219 .iter()
220 .map(|&p| stream.update(p).unwrap_or(f64::NAN).to_bits())
221 .collect();
222 assert!(out.iter().zip(&expected).all(|(v, e)| v.to_bits() == *e));
223 assert!(nan.iter().zip(&expected).all(|(v, e)| v.to_bits() == *e));
224 assert!(fast.iter().zip(&expected).all(|(v, e)| v.to_bits() == *e));
225 }
226
227 #[test]
228 fn sine_and_lead_are_functions_of_dc_phase() {
229 let prices = sine_prices(150);
231 let mut sw = SineWave::new();
232 let mut phase = HtDcPhase::new();
233 for &p in &prices {
234 let sine = sw.update(p);
235 let ph = phase.update(p);
236 assert_eq!(sine.is_some(), ph.is_some());
237 if let (Some(s), Some(deg)) = (sine, ph) {
238 let rad = deg.to_radians();
239 assert_eq!(s.to_bits(), rad.sin().to_bits());
240 assert_eq!(
241 sw.lead().to_bits(),
242 (rad + 45f64.to_radians()).sin().to_bits()
243 );
244 }
245 }
246 }
247
248 #[test]
249 fn zero_series_hand_computed() {
250 let mut sw = SineWave::new();
254 let _ = sw.batch(&[0.0; 400]);
255 assert_relative_eq!(sw.value().unwrap(), -(3.0_f64.sqrt()) / 2.0, epsilon = 1e-6);
256 assert_relative_eq!(
257 sw.lead(),
258 -(6.0_f64.sqrt() + 2.0_f64.sqrt()) / 4.0,
259 epsilon = 1e-6
260 );
261 }
262}