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 fn update(&mut self, section: CrossSection) -> Option<f64> {
57 let advancers = section.advancers() as f64;
58 let decliners = section.decliners().max(1) as f64;
59 self.has_emitted = true;
60 Some(advancers / decliners)
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 "AdvanceDeclineRatio"
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));
97 CrossSection::new(members, 0).unwrap()
98 }
99
100 #[test]
101 fn accessors_and_metadata() {
102 let adr = AdvanceDeclineRatio::new();
103 assert_eq!(adr.name(), "AdvanceDeclineRatio");
104 assert_eq!(adr.warmup_period(), 1);
105 assert!(!adr.is_ready());
106 }
107
108 #[test]
109 fn first_tick_emits_ratio() {
110 let mut adr = AdvanceDeclineRatio::new();
111 assert_eq!(adr.update(section(3, 1)), Some(3.0));
112 assert!(adr.is_ready());
113 }
114
115 #[test]
116 fn zero_decliners_floors_denominator() {
117 let mut adr = AdvanceDeclineRatio::new();
118 assert_eq!(adr.update(section(4, 0)), Some(4.0));
120 }
121
122 #[test]
123 fn no_advancers_yields_zero() {
124 let mut adr = AdvanceDeclineRatio::new();
125 assert_eq!(adr.update(section(0, 5)), Some(0.0));
126 }
127
128 #[test]
129 fn reset_clears_state() {
130 let mut adr = AdvanceDeclineRatio::new();
131 adr.update(section(3, 1));
132 assert!(adr.is_ready());
133 adr.reset();
134 assert!(!adr.is_ready());
135 assert_eq!(adr.update(section(2, 1)), Some(2.0));
136 }
137
138 #[test]
139 fn batch_equals_streaming() {
140 let sections = vec![section(3, 1), section(4, 0), section(0, 5), section(2, 2)];
141 let mut a = AdvanceDeclineRatio::new();
142 let mut b = AdvanceDeclineRatio::new();
143 assert_eq!(
144 a.batch(§ions),
145 sections
146 .iter()
147 .map(|s| b.update(s.clone()))
148 .collect::<Vec<_>>()
149 );
150 }
151}