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 fn update(&mut self, section: CrossSection) -> Option<f64> {
62 let net = section.advancers() as f64 - section.decliners() as f64;
63 self.line += net;
64 self.has_emitted = true;
65 Some(self.line)
66 }
67
68 fn reset(&mut self) {
69 self.line = 0.0;
70 self.has_emitted = false;
71 }
72
73 fn warmup_period(&self) -> usize {
74 1
75 }
76
77 fn is_ready(&self) -> bool {
78 self.has_emitted
79 }
80
81 fn name(&self) -> &'static str {
82 "AdvanceDecline"
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use crate::cross_section::Member;
90 use crate::traits::BatchExt;
91
92 fn section(up: usize, down: usize, flat: usize) -> CrossSection {
95 let mut members = Vec::new();
96 for _ in 0..up {
97 members.push(Member::new(1.0, 10.0, false, false));
98 }
99 for _ in 0..down {
100 members.push(Member::new(-1.0, 10.0, false, false));
101 }
102 for _ in 0..flat {
103 members.push(Member::new(0.0, 10.0, false, false));
104 }
105 CrossSection::new(members, 0).unwrap()
106 }
107
108 #[test]
109 fn accessors_and_metadata() {
110 let ad = AdvanceDecline::new();
111 assert_eq!(ad.name(), "AdvanceDecline");
112 assert_eq!(ad.warmup_period(), 1);
113 assert!(!ad.is_ready());
114 }
115
116 #[test]
117 fn first_tick_emits_net_breadth() {
118 let mut ad = AdvanceDecline::new();
119 assert_eq!(ad.update(section(3, 1, 0)), Some(2.0));
120 assert!(ad.is_ready());
121 }
122
123 #[test]
124 fn line_accumulates_across_ticks() {
125 let mut ad = AdvanceDecline::new();
126 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)); }
130
131 #[test]
132 fn unchanged_symbols_are_ignored() {
133 let mut ad = AdvanceDecline::new();
134 assert_eq!(ad.update(section(2, 2, 5)), Some(0.0));
136 assert_eq!(ad.update(section(2, 2, 5)), Some(0.0));
137 }
138
139 #[test]
140 fn reset_clears_state() {
141 let mut ad = AdvanceDecline::new();
142 ad.update(section(5, 0, 0));
143 assert!(ad.is_ready());
144 ad.reset();
145 assert!(!ad.is_ready());
146 assert_eq!(ad.update(section(1, 0, 0)), Some(1.0));
148 }
149
150 #[test]
151 fn batch_equals_streaming() {
152 let sections = vec![
153 section(3, 1, 2),
154 section(1, 4, 0),
155 section(2, 2, 1),
156 section(5, 0, 3),
157 ];
158 let mut a = AdvanceDecline::new();
159 let mut b = AdvanceDecline::new();
160 assert_eq!(
161 a.batch(§ions),
162 sections
163 .iter()
164 .map(|s| b.update(s.clone()))
165 .collect::<Vec<_>>()
166 );
167 }
168}