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