wickra_core/indicators/
percent_above_ma.rs1use crate::cross_section::CrossSection;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
40pub struct PercentAboveMa {
41 has_emitted: bool,
42}
43
44impl PercentAboveMa {
45 #[must_use]
47 pub const fn new() -> Self {
48 Self { has_emitted: false }
49 }
50}
51
52impl Indicator for PercentAboveMa {
53 type Input = CrossSection;
54 type Output = f64;
55
56 fn update(&mut self, section: CrossSection) -> Option<f64> {
57 let above = section.above_ma_count() as f64;
58 let total = section.members.len() as f64;
59 self.has_emitted = true;
60 Some(100.0 * above / total)
61 }
62
63 fn reset(&mut self) {
64 self.has_emitted = false;
65 }
66
67 fn warmup_period(&self) -> usize {
68 1
69 }
70
71 fn is_ready(&self) -> bool {
72 self.has_emitted
73 }
74
75 fn name(&self) -> &'static str {
76 "PercentAboveMa"
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::cross_section::Member;
84 use crate::traits::BatchExt;
85
86 fn tick(above: usize, below: usize) -> CrossSection {
87 let mut members = Vec::new();
88 for _ in 0..above {
89 members.push(Member::with_signals(1.0, 10.0, false, false, true, false));
90 }
91 for _ in 0..below {
92 members.push(Member::with_signals(-1.0, 10.0, false, false, false, false));
93 }
94 CrossSection::new(members, 0).unwrap()
95 }
96
97 #[test]
98 fn accessors_and_metadata() {
99 let pct = PercentAboveMa::new();
100 assert_eq!(pct.name(), "PercentAboveMa");
101 assert_eq!(pct.warmup_period(), 1);
102 assert!(!pct.is_ready());
103 }
104
105 #[test]
106 fn first_tick_emits_percentage() {
107 let mut pct = PercentAboveMa::new();
108 assert_eq!(pct.update(tick(3, 1)), Some(75.0));
109 assert!(pct.is_ready());
110 }
111
112 #[test]
113 fn all_above_is_one_hundred() {
114 let mut pct = PercentAboveMa::new();
115 assert_eq!(pct.update(tick(4, 0)), Some(100.0));
116 }
117
118 #[test]
119 fn none_above_is_zero() {
120 let mut pct = PercentAboveMa::new();
121 assert_eq!(pct.update(tick(0, 5)), Some(0.0));
122 }
123
124 #[test]
125 fn reset_clears_state() {
126 let mut pct = PercentAboveMa::new();
127 pct.update(tick(3, 1));
128 assert!(pct.is_ready());
129 pct.reset();
130 assert!(!pct.is_ready());
131 }
132
133 #[test]
134 fn batch_equals_streaming() {
135 let sections = vec![tick(3, 1), tick(4, 0), tick(0, 5)];
136 let mut a = PercentAboveMa::new();
137 let mut b = PercentAboveMa::new();
138 assert_eq!(
139 a.batch(§ions),
140 sections
141 .iter()
142 .map(|s| b.update(s.clone()))
143 .collect::<Vec<_>>()
144 );
145 }
146}