wickra_core/indicators/
absolute_breadth_index.rs1use crate::cross_section::CrossSection;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
41pub struct AbsoluteBreadthIndex {
42 has_emitted: bool,
43}
44
45impl AbsoluteBreadthIndex {
46 #[must_use]
48 pub const fn new() -> Self {
49 Self { has_emitted: false }
50 }
51}
52
53impl Indicator for AbsoluteBreadthIndex {
54 type Input = CrossSection;
55 type Output = f64;
56
57 fn update(&mut self, section: CrossSection) -> Option<f64> {
58 let net = section.advancers() as f64 - section.decliners() as f64;
59 self.has_emitted = true;
60 Some(net.abs())
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 "AbsoluteBreadthIndex"
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 section(up: usize, down: usize) -> CrossSection {
87 let mut members = Vec::new();
88 for _ in 0..up {
89 members.push(Member::new(1.0, 10.0, false, false));
90 }
91 for _ in 0..down {
92 members.push(Member::new(-1.0, 10.0, false, false));
93 }
94 members.push(Member::new(0.0, 10.0, false, false));
95 CrossSection::new(members, 0).unwrap()
96 }
97
98 #[test]
99 fn accessors_and_metadata() {
100 let abi = AbsoluteBreadthIndex::new();
101 assert_eq!(abi.name(), "AbsoluteBreadthIndex");
102 assert_eq!(abi.warmup_period(), 1);
103 assert!(!abi.is_ready());
104 }
105
106 #[test]
107 fn magnitude_ignores_direction() {
108 let mut abi = AbsoluteBreadthIndex::new();
109 assert_eq!(abi.update(section(2, 5)), Some(3.0));
110 let mut abi2 = AbsoluteBreadthIndex::new();
112 assert_eq!(abi2.update(section(5, 2)), Some(3.0));
113 }
114
115 #[test]
116 fn balanced_universe_yields_zero() {
117 let mut abi = AbsoluteBreadthIndex::new();
118 assert_eq!(abi.update(section(3, 3)), Some(0.0));
119 assert!(abi.is_ready());
120 }
121
122 #[test]
123 fn reset_clears_state() {
124 let mut abi = AbsoluteBreadthIndex::new();
125 abi.update(section(2, 5));
126 assert!(abi.is_ready());
127 abi.reset();
128 assert!(!abi.is_ready());
129 }
130
131 #[test]
132 fn batch_equals_streaming() {
133 let sections = vec![section(2, 5), section(5, 2), section(3, 3)];
134 let mut a = AbsoluteBreadthIndex::new();
135 let mut b = AbsoluteBreadthIndex::new();
136 assert_eq!(
137 a.batch(§ions),
138 sections
139 .iter()
140 .map(|s| b.update(s.clone()))
141 .collect::<Vec<_>>()
142 );
143 }
144}