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 #[inline]
58 fn update(&mut self, section: CrossSection) -> Option<f64> {
59 let net = section.advancers() as f64 - section.decliners() as f64;
60 self.has_emitted = true;
61 Some(net.abs())
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 "AbsoluteBreadthIndex"
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 section(up: usize, down: usize) -> CrossSection {
91 let mut members = Vec::new();
92 for _ in 0..up {
93 members.push(Member::new(1.0, 10.0, false, false));
94 }
95 for _ in 0..down {
96 members.push(Member::new(-1.0, 10.0, false, false));
97 }
98 members.push(Member::new(0.0, 10.0, false, false));
99 CrossSection::new(members, 0).unwrap()
100 }
101
102 #[test]
103 fn accessors_and_metadata() {
104 let abi = AbsoluteBreadthIndex::new();
105 assert_eq!(abi.name(), "AbsoluteBreadthIndex");
106 assert_eq!(abi.warmup_period(), 1);
107 assert!(!abi.is_ready());
108 }
109
110 #[test]
111 fn magnitude_ignores_direction() {
112 let mut abi = AbsoluteBreadthIndex::new();
113 assert_eq!(abi.update(section(2, 5)), Some(3.0));
114 let mut abi2 = AbsoluteBreadthIndex::new();
116 assert_eq!(abi2.update(section(5, 2)), Some(3.0));
117 }
118
119 #[test]
120 fn balanced_universe_yields_zero() {
121 let mut abi = AbsoluteBreadthIndex::new();
122 assert_eq!(abi.update(section(3, 3)), Some(0.0));
123 assert!(abi.is_ready());
124 }
125
126 #[test]
127 fn reset_clears_state() {
128 let mut abi = AbsoluteBreadthIndex::new();
129 abi.update(section(2, 5));
130 assert!(abi.is_ready());
131 abi.reset();
132 assert!(!abi.is_ready());
133 }
134
135 #[test]
136 fn batch_equals_streaming() {
137 let sections = vec![section(2, 5), section(5, 2), section(3, 3)];
138 let mut a = AbsoluteBreadthIndex::new();
139 let mut b = AbsoluteBreadthIndex::new();
140 assert_eq!(
141 a.batch(§ions),
142 sections
143 .iter()
144 .map(|s| b.update(s.clone()))
145 .collect::<Vec<_>>()
146 );
147 }
148}