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
66 #[test]
67 fn test_cyber_cycle_features_basic() {
68 let mut extractor = CyberCycleFeatureExtractor::new(14);
69
70 let mut max_abs = 0.0f64;
71 for i in 0..50 {
73 let val = 100.0 + 5.0 * (i as f64 * 0.3).sin();
74 let f = extractor.next(val);
75 if !f.cycle.is_nan() {
76 max_abs = max_abs.max(f.cycle.abs());
77 }
78 }
79 assert!(max_abs < 100.0, "observed max |cycle| = {}", max_abs);
82 }
83}