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 fn update(&mut self, book: OrderBook) -> Option<f64> {
54 self.has_emitted = true;
55 let bid_depth: f64 = book.bids.iter().map(|l| l.size).sum();
56 let ask_depth: f64 = book.asks.iter().map(|l| l.size).sum();
57 let total = bid_depth + ask_depth;
58 if total <= 0.0 {
59 return Some(0.0);
60 }
61 Some((bid_depth - ask_depth) / total)
62 }
63
64 fn reset(&mut self) {
65 self.has_emitted = false;
66 }
67
68 fn warmup_period(&self) -> usize {
69 1
70 }
71
72 fn is_ready(&self) -> bool {
73 self.has_emitted
74 }
75
76 fn name(&self) -> &'static str {
77 "OrderBookImbalanceFull"
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use crate::microstructure::Level;
85 use crate::traits::BatchExt;
86
87 fn book(bids: &[(f64, f64)], asks: &[(f64, f64)]) -> OrderBook {
88 let to_levels = |xs: &[(f64, f64)]| {
89 xs.iter()
90 .map(|&(p, s)| Level::new(p, s).unwrap())
91 .collect::<Vec<_>>()
92 };
93 OrderBook::new(to_levels(bids), to_levels(asks)).unwrap()
94 }
95
96 #[test]
97 fn accessors_and_metadata() {
98 let obi = OrderBookImbalanceFull::new();
99 assert_eq!(obi.name(), "OrderBookImbalanceFull");
100 assert_eq!(obi.warmup_period(), 1);
101 assert!(!obi.is_ready());
102 }
103
104 #[test]
105 fn sums_full_depth() {
106 let mut obi = OrderBookImbalanceFull::new();
107 let b = book(&[(100.0, 2.0), (99.0, 2.0)], &[(101.0, 1.0), (102.0, 1.0)]);
108 assert_eq!(obi.update(b), Some(1.0 / 3.0));
110 assert!(obi.is_ready());
111 }
112
113 #[test]
114 fn ask_heavy_full_depth_is_negative() {
115 let mut obi = OrderBookImbalanceFull::new();
116 let b = book(&[(100.0, 1.0)], &[(101.0, 2.0), (102.0, 1.0)]);
117 assert_eq!(obi.update(b), Some(-0.5));
119 }
120
121 #[test]
122 fn zero_size_is_zero() {
123 let mut obi = OrderBookImbalanceFull::new();
124 assert_eq!(
125 obi.update(book(&[(100.0, 0.0)], &[(101.0, 0.0)])),
126 Some(0.0)
127 );
128 }
129
130 #[test]
131 fn batch_equals_streaming() {
132 let books: Vec<OrderBook> = (0..20)
133 .map(|i| {
134 let bid = 1.0 + f64::from(i % 3);
135 book(&[(100.0, bid), (99.0, 1.0)], &[(101.0, 2.0), (102.0, 1.0)])
136 })
137 .collect();
138 let mut a = OrderBookImbalanceFull::new();
139 let mut b = OrderBookImbalanceFull::new();
140 assert_eq!(
141 a.batch(&books),
142 books
143 .iter()
144 .map(|x| b.update(x.clone()))
145 .collect::<Vec<_>>()
146 );
147 }
148
149 #[test]
150 fn reset_clears_state() {
151 let mut obi = OrderBookImbalanceFull::new();
152 obi.update(book(&[(100.0, 1.0)], &[(101.0, 1.0)]));
153 assert!(obi.is_ready());
154 obi.reset();
155 assert!(!obi.is_ready());
156 }
157}