wickra_core/indicators/
advance_decline.rs1use crate::cross_section::CrossSection;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
41pub struct AdvanceDecline {
42 line: f64,
43 has_emitted: bool,
44}
45
46impl AdvanceDecline {
47 #[must_use]
49 pub const fn new() -> Self {
50 Self {
51 line: 0.0,
52 has_emitted: false,
53 }
54 }
55}
56
57impl Indicator for AdvanceDecline {
58 type Input = CrossSection;
59 type Output = f64;
60
61 #[inline]
62 fn update(&mut self, section: CrossSection) -> Option<f64> {
63 let net = section.advancers() as f64 - section.decliners() as f64;
64 self.line += net;
65 self.has_emitted = true;
66 Some(self.line)
67 }
68
69 fn reset(&mut self) {
70 self.line = 0.0;
71 self.has_emitted = false;
72 }
73
74 #[inline]
75 fn warmup_period(&self) -> usize {
76 1
77 }
78
79 #[inline]
80 fn is_ready(&self) -> bool {
81 self.has_emitted
82 }
83
84 #[inline]
85 fn name(&self) -> &'static str {
86 "AdvanceDecline"
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::cross_section::Member;
94 use crate::traits::BatchExt;
95
96 fn section(up: usize, down: usize, flat: usize) -> CrossSection {
99 let mut members = Vec::new();
100 for _ in 0..up {
101 members.push(Member::new(1.0, 10.0, false, false));
102 }
103 for _ in 0..down {
104 members.push(Member::new(-1.0, 10.0, false, false));
105 }
106 for _ in 0..flat {
107 members.push(Member::new(0.0, 10.0, false, false));
108 }
109 CrossSection::new(members, 0).unwrap()
110 }
111
112 #[test]
113 fn accessors_and_metadata() {
114 let ad = AdvanceDecline::new();
115 assert_eq!(ad.name(), "AdvanceDecline");
116 assert_eq!(ad.warmup_period(), 1);
117 assert!(!ad.is_ready());
118 }
119
120 #[test]
121 fn first_tick_emits_net_breadth() {
122 let mut ad = AdvanceDecline::new();
123 assert_eq!(ad.update(section(3, 1, 0)), Some(2.0));
124 assert!(ad.is_ready());
125 }
126
127 #[test]
128 fn line_accumulates_across_ticks() {
129 let mut ad = AdvanceDecline::new();
130 assert_eq!(ad.update(section(3, 1, 0)), Some(2.0)); assert_eq!(ad.update(section(1, 4, 0)), Some(-1.0)); assert_eq!(ad.update(section(2, 0, 0)), Some(1.0)); }
134
135 #[test]
136 fn unchanged_symbols_are_ignored() {
137 let mut ad = AdvanceDecline::new();
138 assert_eq!(ad.update(section(2, 2, 5)), Some(0.0));
140 assert_eq!(ad.update(section(2, 2, 5)), Some(0.0));
141 }
142
143 #[test]
144 fn reset_clears_state() {
145 let mut ad = AdvanceDecline::new();
146 ad.update(section(5, 0, 0));
147 assert!(ad.is_ready());
148 ad.reset();
149 assert!(!ad.is_ready());
150 assert_eq!(ad.update(section(1, 0, 0)), Some(1.0));
152 }
153
154 #[test]
155 fn batch_equals_streaming() {
156 let sections = vec![
157 section(3, 1, 2),
158 section(1, 4, 0),
159 section(2, 2, 1),
160 section(5, 0, 3),
161 ];
162 let mut a = AdvanceDecline::new();
163 let mut b = AdvanceDecline::new();
164 assert_eq!(
165 a.batch(§ions),
166 sections
167 .iter()
168 .map(|s| b.update(s.clone()))
169 .collect::<Vec<_>>()
170 );
171 }
172}