quantwave_core/features/
cyber_cycle.rs1use crate::indicators::cyber_cycle::CyberCycle;
10use crate::traits::Next;
11
12#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct CyberCycleFeatures {
15 pub cycle: f64,
16 pub trigger: f64,
17 pub cycle_momentum: f64,
19 pub trigger_signal: f64,
21}
22
23#[derive(Debug, Clone)]
24pub struct CyberCycleFeatureExtractor {
25 inner: CyberCycle,
26 prev_cycle: f64,
27}
28
29impl CyberCycleFeatureExtractor {
30 pub fn new(length: usize) -> Self {
31 Self {
32 inner: CyberCycle::new(length),
33 prev_cycle: 0.0,
34 }
35 }
36}
37
38impl Next<f64> for CyberCycleFeatureExtractor {
39 type Output = CyberCycleFeatures;
40
41 fn next(&mut self, input: f64) -> Self::Output {
42 let (cycle, trigger) = self.inner.next(input);
43
44 let momentum = if self.prev_cycle == 0.0 {
45 0.0
46 } else {
47 cycle - self.prev_cycle
48 };
49 let trigger_signal = (cycle - trigger).signum();
50
51 self.prev_cycle = cycle;
52
53 CyberCycleFeatures {
54 cycle,
55 trigger,
56 cycle_momentum: momentum,
57 trigger_signal,
58 }
59 }
60}
61
62#[cfg(test)]
63mod tests {
64 use super::*;
65 use approx::assert_relative_eq;
66
67 #[test]
68 fn test_cyber_cycle_features_basic() {
69 let mut extractor = CyberCycleFeatureExtractor::new(14);
70
71 let mut max_abs = 0.0f64;
72 for i in 0..50 {
74 let val = 100.0 + 5.0 * (i as f64 * 0.3).sin();
75 let f = extractor.next(val);
76 if !f.cycle.is_nan() {
77 max_abs = max_abs.max(f.cycle.abs());
78 }
79 }
80 assert!(max_abs < 100.0, "observed max |cycle| = {}", max_abs);
83 }
84}