wickra_core/indicators/
trin.rs1use crate::cross_section::CrossSection;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
42pub struct Trin {
43 has_emitted: bool,
44}
45
46impl Trin {
47 #[must_use]
49 pub const fn new() -> Self {
50 Self { has_emitted: false }
51 }
52}
53
54impl Indicator for Trin {
55 type Input = CrossSection;
56 type Output = f64;
57
58 #[inline]
59 fn update(&mut self, section: CrossSection) -> Option<f64> {
60 let advancers = section.advancers() as f64;
61 let decliners = section.decliners().max(1) as f64;
62 let advancing_volume = section.advancing_volume().max(1.0);
63 let declining_volume = section.declining_volume().max(1.0);
64 let ad_ratio = advancers / decliners;
65 let volume_ratio = advancing_volume / declining_volume;
66 self.has_emitted = true;
67 Some(ad_ratio / volume_ratio)
68 }
69
70 fn reset(&mut self) {
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 "Trin"
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 tick(items: &[(f64, f64)]) -> CrossSection {
97 CrossSection::new(
98 items
99 .iter()
100 .map(|&(change, volume)| Member::new(change, volume, false, false))
101 .collect(),
102 0,
103 )
104 .unwrap()
105 }
106
107 #[test]
108 fn accessors_and_metadata() {
109 let trin = Trin::new();
110 assert_eq!(trin.name(), "Trin");
111 assert_eq!(trin.warmup_period(), 1);
112 assert!(!trin.is_ready());
113 }
114
115 #[test]
116 fn balanced_breadth_yields_one() {
117 let mut trin = Trin::new();
118 let value = trin
119 .update(tick(&[(1.0, 50.0), (1.0, 50.0), (1.0, 50.0), (-1.0, 50.0)]))
120 .unwrap();
121 assert!((value - 1.0).abs() < 1e-9);
122 assert!(trin.is_ready());
123 }
124
125 #[test]
126 fn zero_decliners_and_volume_are_floored() {
127 let mut trin = Trin::new();
128 let value = trin.update(tick(&[(1.0, 50.0), (1.0, 50.0)])).unwrap();
131 assert!((value - 0.02).abs() < 1e-9);
132 }
133
134 #[test]
135 fn heavy_declining_volume_pushes_above_one() {
136 let mut trin = Trin::new();
137 let value = trin
139 .update(tick(&[
140 (1.0, 10.0),
141 (1.0, 10.0),
142 (-1.0, 40.0),
143 (-1.0, 40.0),
144 ]))
145 .unwrap();
146 assert!((value - 4.0).abs() < 1e-9);
147 }
148
149 #[test]
150 fn reset_clears_state() {
151 let mut trin = Trin::new();
152 trin.update(tick(&[(1.0, 10.0), (-1.0, 10.0)]));
153 assert!(trin.is_ready());
154 trin.reset();
155 assert!(!trin.is_ready());
156 }
157
158 #[test]
159 fn batch_equals_streaming() {
160 let sections = vec![
161 tick(&[(1.0, 50.0), (1.0, 50.0), (1.0, 50.0), (-1.0, 50.0)]),
162 tick(&[(1.0, 50.0), (1.0, 50.0)]),
163 tick(&[(1.0, 10.0), (1.0, 10.0), (-1.0, 40.0), (-1.0, 40.0)]),
164 ];
165 let mut a = Trin::new();
166 let mut b = Trin::new();
167 assert_eq!(
168 a.batch(§ions),
169 sections
170 .iter()
171 .map(|s| b.update(s.clone()))
172 .collect::<Vec<_>>()
173 );
174 }
175}