wickra_core/indicators/
advance_decline_ratio.rs1use crate::cross_section::CrossSection;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
40pub struct AdvanceDeclineRatio {
41 has_emitted: bool,
42}
43
44impl AdvanceDeclineRatio {
45 #[must_use]
47 pub const fn new() -> Self {
48 Self { has_emitted: false }
49 }
50}
51
52impl Indicator for AdvanceDeclineRatio {
53 type Input = CrossSection;
54 type Output = f64;
55
56 #[inline]
57 fn update(&mut self, section: CrossSection) -> Option<f64> {
58 let advancers = section.advancers() as f64;
59 let decliners = section.decliners().max(1) as f64;
60 self.has_emitted = true;
61 Some(advancers / decliners)
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 "AdvanceDeclineRatio"
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));
101 CrossSection::new(members, 0).unwrap()
102 }
103
104 #[test]
105 fn accessors_and_metadata() {
106 let adr = AdvanceDeclineRatio::new();
107 assert_eq!(adr.name(), "AdvanceDeclineRatio");
108 assert_eq!(adr.warmup_period(), 1);
109 assert!(!adr.is_ready());
110 }
111
112 #[test]
113 fn first_tick_emits_ratio() {
114 let mut adr = AdvanceDeclineRatio::new();
115 assert_eq!(adr.update(section(3, 1)), Some(3.0));
116 assert!(adr.is_ready());
117 }
118
119 #[test]
120 fn zero_decliners_floors_denominator() {
121 let mut adr = AdvanceDeclineRatio::new();
122 assert_eq!(adr.update(section(4, 0)), Some(4.0));
124 }
125
126 #[test]
127 fn no_advancers_yields_zero() {
128 let mut adr = AdvanceDeclineRatio::new();
129 assert_eq!(adr.update(section(0, 5)), Some(0.0));
130 }
131
132 #[test]
133 fn reset_clears_state() {
134 let mut adr = AdvanceDeclineRatio::new();
135 adr.update(section(3, 1));
136 assert!(adr.is_ready());
137 adr.reset();
138 assert!(!adr.is_ready());
139 assert_eq!(adr.update(section(2, 1)), Some(2.0));
140 }
141
142 #[test]
143 fn batch_equals_streaming() {
144 let sections = vec![section(3, 1), section(4, 0), section(0, 5), section(2, 2)];
145 let mut a = AdvanceDeclineRatio::new();
146 let mut b = AdvanceDeclineRatio::new();
147 assert_eq!(
148 a.batch(§ions),
149 sections
150 .iter()
151 .map(|s| b.update(s.clone()))
152 .collect::<Vec<_>>()
153 );
154 }
155}