wickra_core/indicators/
ob_imbalance_full.rs1use crate::microstructure::OrderBook;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
38pub struct OrderBookImbalanceFull {
39 has_emitted: bool,
40}
41
42impl OrderBookImbalanceFull {
43 pub const fn new() -> Self {
45 Self { has_emitted: false }
46 }
47}
48
49impl Indicator for OrderBookImbalanceFull {
50 type Input = OrderBook;
51 type Output = f64;
52
53 #[inline]
54 fn update(&mut self, book: OrderBook) -> Option<f64> {
55 self.has_emitted = true;
56 let bid_depth: f64 = book.bids.iter().map(|l| l.size).sum();
57 let ask_depth: f64 = book.asks.iter().map(|l| l.size).sum();
58 let total = bid_depth + ask_depth;
59 if total <= 0.0 {
60 return Some(0.0);
61 }
62 Some((bid_depth - ask_depth) / total)
63 }
64
65 fn reset(&mut self) {
66 self.has_emitted = 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.has_emitted
77 }
78
79 #[inline]
80 fn name(&self) -> &'static str {
81 "OrderBookImbalanceFull"
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use crate::microstructure::Level;
89 use crate::traits::BatchExt;
90
91 fn book(bids: &[(f64, f64)], asks: &[(f64, f64)]) -> OrderBook {
92 let to_levels = |xs: &[(f64, f64)]| {
93 xs.iter()
94 .map(|&(p, s)| Level::new(p, s).unwrap())
95 .collect::<Vec<_>>()
96 };
97 OrderBook::new(to_levels(bids), to_levels(asks)).unwrap()
98 }
99
100 #[test]
101 fn accessors_and_metadata() {
102 let obi = OrderBookImbalanceFull::new();
103 assert_eq!(obi.name(), "OrderBookImbalanceFull");
104 assert_eq!(obi.warmup_period(), 1);
105 assert!(!obi.is_ready());
106 }
107
108 #[test]
109 fn sums_full_depth() {
110 let mut obi = OrderBookImbalanceFull::new();
111 let b = book(&[(100.0, 2.0), (99.0, 2.0)], &[(101.0, 1.0), (102.0, 1.0)]);
112 assert_eq!(obi.update(b), Some(1.0 / 3.0));
114 assert!(obi.is_ready());
115 }
116
117 #[test]
118 fn ask_heavy_full_depth_is_negative() {
119 let mut obi = OrderBookImbalanceFull::new();
120 let b = book(&[(100.0, 1.0)], &[(101.0, 2.0), (102.0, 1.0)]);
121 assert_eq!(obi.update(b), Some(-0.5));
123 }
124
125 #[test]
126 fn zero_size_is_zero() {
127 let mut obi = OrderBookImbalanceFull::new();
128 assert_eq!(
129 obi.update(book(&[(100.0, 0.0)], &[(101.0, 0.0)])),
130 Some(0.0)
131 );
132 }
133
134 #[test]
135 fn batch_equals_streaming() {
136 let books: Vec<OrderBook> = (0..20)
137 .map(|i| {
138 let bid = 1.0 + f64::from(i % 3);
139 book(&[(100.0, bid), (99.0, 1.0)], &[(101.0, 2.0), (102.0, 1.0)])
140 })
141 .collect();
142 let mut a = OrderBookImbalanceFull::new();
143 let mut b = OrderBookImbalanceFull::new();
144 assert_eq!(
145 a.batch(&books),
146 books
147 .iter()
148 .map(|x| b.update(x.clone()))
149 .collect::<Vec<_>>()
150 );
151 }
152
153 #[test]
154 fn reset_clears_state() {
155 let mut obi = OrderBookImbalanceFull::new();
156 obi.update(book(&[(100.0, 1.0)], &[(101.0, 1.0)]));
157 assert!(obi.is_ready());
158 obi.reset();
159 assert!(!obi.is_ready());
160 }
161}