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