wickra_core/indicators/
adaptive_cycle.rs1use crate::indicators::hilbert_dominant_cycle::HilbertDominantCycle;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
30pub struct AdaptiveCycle {
31 cycle: HilbertDominantCycle,
32 last_value: Option<f64>,
33}
34
35impl AdaptiveCycle {
36 pub fn new() -> Self {
38 Self::default()
39 }
40
41 pub const fn value(&self) -> Option<f64> {
43 self.last_value
44 }
45}
46
47impl Indicator for AdaptiveCycle {
48 type Input = f64;
49 type Output = f64;
50
51 fn update(&mut self, input: f64) -> Option<f64> {
52 let period = self.cycle.update(input)?;
53 let half = (period * 0.5).round().clamp(3.0, 25.0);
54 self.last_value = Some(half);
55 Some(half)
56 }
57
58 fn reset(&mut self) {
59 self.cycle.reset();
60 self.last_value = None;
61 }
62
63 fn warmup_period(&self) -> usize {
64 self.cycle.warmup_period()
65 }
66
67 fn is_ready(&self) -> bool {
68 self.last_value.is_some()
69 }
70
71 fn name(&self) -> &'static str {
72 "AdaptiveCycle"
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use crate::traits::BatchExt;
80
81 #[test]
82 fn accessors_and_metadata() {
83 let mut ac = AdaptiveCycle::new();
84 assert_eq!(ac.warmup_period(), 50);
85 assert_eq!(ac.name(), "AdaptiveCycle");
86 assert!(!ac.is_ready());
87 assert!(ac.value().is_none());
88 let prices: Vec<f64> = (0..120)
89 .map(|i| 100.0 + (f64::from(i) * 0.4).sin() * 5.0)
90 .collect();
91 ac.batch(&prices);
92 assert!(ac.is_ready());
93 assert!(ac.value().is_some());
94 }
95
96 #[test]
97 fn output_within_clamp_band() {
98 let prices: Vec<f64> = (0..200)
99 .map(|i| 100.0 + (f64::from(i) * 0.5).sin() * 5.0)
100 .collect();
101 let mut ac = AdaptiveCycle::new();
102 for v in ac.batch(&prices).into_iter().flatten() {
103 assert!((3.0..=25.0).contains(&v), "period {v} out of band");
104 assert_eq!(v, v.round(), "expected integer-valued output");
105 }
106 }
107
108 #[test]
109 fn batch_equals_streaming() {
110 let prices: Vec<f64> = (0..200)
111 .map(|i| 100.0 + (f64::from(i) * 0.3).sin() * 5.0)
112 .collect();
113 let mut a = AdaptiveCycle::new();
114 let mut b = AdaptiveCycle::new();
115 let batch = a.batch(&prices);
116 let streamed: Vec<_> = prices.iter().map(|p| b.update(*p)).collect();
117 assert_eq!(batch, streamed);
118 }
119
120 #[test]
121 fn ignores_non_finite_input() {
122 let mut ac = AdaptiveCycle::new();
123 let prices: Vec<f64> = (0..120)
124 .map(|i| 100.0 + (f64::from(i) * 0.4).sin() * 5.0)
125 .collect();
126 ac.batch(&prices);
127 let before = ac.value();
128 assert!(before.is_some());
129 assert_eq!(ac.update(f64::NAN), before);
130 }
131
132 #[test]
133 fn reset_clears_state() {
134 let mut ac = AdaptiveCycle::new();
135 let prices: Vec<f64> = (0..120)
136 .map(|i| 100.0 + (f64::from(i) * 0.4).sin() * 5.0)
137 .collect();
138 ac.batch(&prices);
139 assert!(ac.is_ready());
140 ac.reset();
141 assert!(!ac.is_ready());
142 }
143}