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 fn update(&mut self, section: CrossSection) -> Option<f64> {
57 let net = section.advancers() as f64 - section.decliners() as f64;
58 self.has_emitted = true;
59 Some(net)
60 }
61
62 fn reset(&mut self) {
63 self.has_emitted = false;
64 }
65
66 fn warmup_period(&self) -> usize {
67 1
68 }
69
70 fn is_ready(&self) -> bool {
71 self.has_emitted
72 }
73
74 fn name(&self) -> &'static str {
75 "TickIndex"
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use crate::cross_section::Member;
83 use crate::traits::BatchExt;
84
85 fn section(up: usize, down: usize) -> CrossSection {
86 let mut members = Vec::new();
87 for _ in 0..up {
88 members.push(Member::new(1.0, 10.0, false, false));
89 }
90 for _ in 0..down {
91 members.push(Member::new(-1.0, 10.0, false, false));
92 }
93 members.push(Member::new(0.0, 10.0, false, false));
94 CrossSection::new(members, 0).unwrap()
95 }
96
97 #[test]
98 fn accessors_and_metadata() {
99 let tick = TickIndex::new();
100 assert_eq!(tick.name(), "TickIndex");
101 assert_eq!(tick.warmup_period(), 1);
102 assert!(!tick.is_ready());
103 }
104
105 #[test]
106 fn positive_when_advancers_lead() {
107 let mut tick = TickIndex::new();
108 assert_eq!(tick.update(section(5, 2)), Some(3.0));
109 assert!(tick.is_ready());
110 }
111
112 #[test]
113 fn negative_when_decliners_lead() {
114 let mut tick = TickIndex::new();
115 assert_eq!(tick.update(section(2, 5)), Some(-3.0));
116 }
117
118 #[test]
119 fn does_not_accumulate() {
120 let mut tick = TickIndex::new();
121 assert_eq!(tick.update(section(3, 0)), Some(3.0));
123 assert_eq!(tick.update(section(0, 1)), Some(-1.0));
124 }
125
126 #[test]
127 fn reset_clears_state() {
128 let mut tick = TickIndex::new();
129 tick.update(section(3, 0));
130 assert!(tick.is_ready());
131 tick.reset();
132 assert!(!tick.is_ready());
133 }
134
135 #[test]
136 fn batch_equals_streaming() {
137 let sections = vec![section(5, 2), section(2, 5), section(3, 0), section(0, 1)];
138 let mut a = TickIndex::new();
139 let mut b = TickIndex::new();
140 assert_eq!(
141 a.batch(§ions),
142 sections
143 .iter()
144 .map(|s| b.update(s.clone()))
145 .collect::<Vec<_>>()
146 );
147 }
148}