wickra_core/indicators/
estimated_leverage_ratio.rs1use crate::traits::Indicator;
4
5#[derive(Debug, Clone, Default)]
34pub struct EstimatedLeverageRatio {
35 ready: bool,
36}
37
38impl EstimatedLeverageRatio {
39 #[must_use]
41 pub const fn new() -> Self {
42 Self { ready: false }
43 }
44}
45
46impl Indicator for EstimatedLeverageRatio {
47 type Input = (f64, f64);
48 type Output = f64;
49
50 #[inline]
51 fn update(&mut self, input: (f64, f64)) -> Option<f64> {
52 let (open_interest, reserve) = input;
53 if !open_interest.is_finite() || !reserve.is_finite() {
54 return None;
55 }
56 let elr = if reserve > 0.0 {
57 open_interest / reserve
58 } else {
59 0.0
60 };
61 self.ready = true;
62 Some(elr)
63 }
64
65 fn reset(&mut self) {
66 self.ready = false;
67 }
68
69 #[inline]
70 fn warmup_period(&self) -> usize {
71 1
72 }
73
74 #[inline]
75 fn is_ready(&self) -> bool {
76 self.ready
77 }
78
79 #[inline]
80 fn name(&self) -> &'static str {
81 "EstimatedLeverageRatio"
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use crate::traits::BatchExt;
89 use approx::assert_relative_eq;
90
91 #[test]
92 fn accessors_and_metadata() {
93 let e = EstimatedLeverageRatio::new();
94 assert_eq!(e.warmup_period(), 1);
95 assert_eq!(e.name(), "EstimatedLeverageRatio");
96 assert!(!e.is_ready());
97 }
98
99 #[test]
100 fn ratio_reference_value() {
101 let mut e = EstimatedLeverageRatio::new();
102 assert_relative_eq!(e.update((1_000.0, 4_000.0)).unwrap(), 0.25, epsilon = 1e-12);
104 }
105
106 #[test]
107 fn higher_oi_raises_ratio() {
108 let mut e = EstimatedLeverageRatio::new();
109 let low = e.update((1_000.0, 10_000.0)).unwrap();
110 let high = e.update((3_000.0, 10_000.0)).unwrap();
111 assert!(high > low);
112 }
113
114 #[test]
115 fn zero_reserve_is_zero() {
116 let mut e = EstimatedLeverageRatio::new();
117 assert_relative_eq!(e.update((1_000.0, 0.0)).unwrap(), 0.0, epsilon = 1e-12);
118 }
119
120 #[test]
121 fn non_finite_input_returns_none() {
122 let mut e = EstimatedLeverageRatio::new();
123 assert_eq!(e.update((f64::NAN, 1.0)), None);
124 assert_eq!(e.update((1.0, f64::INFINITY)), None);
125 assert!(!e.is_ready());
126 }
127
128 #[test]
129 fn ready_after_first_update() {
130 let mut e = EstimatedLeverageRatio::new();
131 assert!(!e.is_ready());
132 e.update((1_000.0, 10_000.0));
133 assert!(e.is_ready());
134 }
135
136 #[test]
137 fn reset_clears_state() {
138 let mut e = EstimatedLeverageRatio::new();
139 e.update((1_000.0, 10_000.0));
140 assert!(e.is_ready());
141 e.reset();
142 assert!(!e.is_ready());
143 }
144
145 #[test]
146 fn batch_equals_streaming() {
147 let pairs: Vec<(f64, f64)> = (0..40)
148 .map(|i| (1_000.0 + f64::from(i) * 10.0, 10_000.0 - f64::from(i)))
149 .collect();
150 let batch = EstimatedLeverageRatio::new().batch(&pairs);
151 let mut b = EstimatedLeverageRatio::new();
152 let streamed: Vec<_> = pairs.iter().map(|x| b.update(*x)).collect();
153 assert_eq!(batch, streamed);
154 }
155
156 #[test]
157 fn warmup_first_value_at_index_zero() {
158 let mut e = EstimatedLeverageRatio::new();
159 assert_eq!(e.update((50.0, 200.0)), Some(0.25));
161 }
162
163 #[test]
164 fn hand_computed_series() {
165 let mut e = EstimatedLeverageRatio::new();
166 assert_eq!(e.update((30_000.0, 120_000.0)), Some(0.25));
168 assert_eq!(e.update((45_000.0, 90_000.0)), Some(0.5));
169 assert_eq!(e.update((90_000.0, 60_000.0)), Some(1.5));
170 assert_eq!(e.update((0.0, 60_000.0)), Some(0.0));
172 }
173
174 #[test]
175 fn negative_reserve_is_zero() {
176 let mut e = EstimatedLeverageRatio::new();
177 assert_eq!(
178 e.update((1_000.0, -5.0)).unwrap().to_bits(),
179 0.0f64.to_bits()
180 );
181 assert_eq!(
182 e.update((1_000.0, -0.0)).unwrap().to_bits(),
183 0.0f64.to_bits()
184 );
185 assert!(e.is_ready());
186 }
187
188 #[test]
189 fn every_non_finite_combination_returns_none() {
190 let mut e = EstimatedLeverageRatio::new();
191 assert_eq!(e.update((f64::INFINITY, 1.0)), None);
192 assert_eq!(e.update((f64::NEG_INFINITY, 1.0)), None);
193 assert_eq!(e.update((1.0, f64::NAN)), None);
194 assert_eq!(e.update((1.0, f64::NEG_INFINITY)), None);
195 assert_eq!(e.update((f64::NAN, f64::NAN)), None);
196 e.update((1.0, 2.0));
198 assert!(e.is_ready());
199 assert_eq!(e.update((f64::NAN, 2.0)), None);
200 assert!(e.is_ready());
201 }
202
203 #[test]
204 fn reset_replays_identically_to_fresh_instance() {
205 let pairs = [
206 (1_000.0, 4_000.0),
207 (f64::NAN, 1.0),
208 (2_000.0, 0.0),
209 (500.0, 250.0),
210 ];
211 let mut e = EstimatedLeverageRatio::new();
212 let first = e.batch(&pairs);
213 e.reset();
214 let second = e.batch(&pairs);
215 let fresh = EstimatedLeverageRatio::default().batch(&pairs);
216 assert_eq!(first, second);
217 assert_eq!(second, fresh);
218 assert_eq!(first, vec![Some(0.25), None, Some(0.0), Some(2.0)]);
219 }
220
221 #[test]
222 fn batch_nan_into_is_bit_identical_to_streaming() {
223 let pairs: Vec<(f64, f64)> = (0..30)
224 .map(|i| match i % 5 {
225 0 => (f64::NAN, 10.0),
226 1 => (100.0, 0.0),
227 _ => (100.0 + f64::from(i) * 3.3, 7.0 + f64::from(i) * 0.7),
228 })
229 .collect();
230 let mut out = vec![0.0; pairs.len()];
231 EstimatedLeverageRatio::new().batch_nan_into(&pairs, &mut out);
232 let mut s = EstimatedLeverageRatio::new();
233 assert!(pairs
234 .iter()
235 .zip(&out)
236 .all(|(p, o)| s.update(*p).unwrap_or(f64::NAN).to_bits() == o.to_bits()));
237 }
238}