wickra_core/indicators/
ob_imbalance_top1.rs1use crate::microstructure::OrderBook;
4use crate::traits::Indicator;
5
6#[derive(Debug, Clone, Default)]
36pub struct OrderBookImbalanceTop1 {
37 has_emitted: bool,
38}
39
40impl OrderBookImbalanceTop1 {
41 pub const fn new() -> Self {
43 Self { has_emitted: false }
44 }
45}
46
47impl Indicator for OrderBookImbalanceTop1 {
48 type Input = OrderBook;
49 type Output = f64;
50
51 fn update(&mut self, book: OrderBook) -> Option<f64> {
52 self.has_emitted = true;
53 let (Some(bid), Some(ask)) = (book.best_bid(), book.best_ask()) else {
54 return Some(0.0);
55 };
56 let total = bid.size + ask.size;
57 if total <= 0.0 {
58 return Some(0.0);
59 }
60 Some((bid.size - ask.size) / total)
61 }
62
63 fn reset(&mut self) {
64 self.has_emitted = false;
65 }
66
67 fn warmup_period(&self) -> usize {
68 1
69 }
70
71 fn is_ready(&self) -> bool {
72 self.has_emitted
73 }
74
75 fn name(&self) -> &'static str {
76 "OrderBookImbalanceTop1"
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::microstructure::Level;
84 use crate::traits::BatchExt;
85
86 fn book(bids: &[(f64, f64)], asks: &[(f64, f64)]) -> OrderBook {
87 let to_levels = |xs: &[(f64, f64)]| {
88 xs.iter()
89 .map(|&(p, s)| Level::new(p, s).unwrap())
90 .collect::<Vec<_>>()
91 };
92 OrderBook::new(to_levels(bids), to_levels(asks)).unwrap()
93 }
94
95 #[test]
96 fn accessors_and_metadata() {
97 let obi = OrderBookImbalanceTop1::new();
98 assert_eq!(obi.name(), "OrderBookImbalanceTop1");
99 assert_eq!(obi.warmup_period(), 1);
100 assert!(!obi.is_ready());
101 }
102
103 #[test]
104 fn balanced_top_is_zero() {
105 let mut obi = OrderBookImbalanceTop1::new();
106 assert_eq!(
107 obi.update(book(&[(100.0, 2.0)], &[(101.0, 2.0)])),
108 Some(0.0)
109 );
110 assert!(obi.is_ready());
111 }
112
113 #[test]
114 fn bid_heavy_is_positive() {
115 let mut obi = OrderBookImbalanceTop1::new();
116 assert_eq!(
117 obi.update(book(&[(100.0, 3.0)], &[(101.0, 1.0)])),
118 Some(0.5)
119 );
120 }
121
122 #[test]
123 fn ask_heavy_is_negative() {
124 let mut obi = OrderBookImbalanceTop1::new();
125 assert_eq!(
126 obi.update(book(&[(100.0, 1.0)], &[(101.0, 3.0)])),
127 Some(-0.5)
128 );
129 }
130
131 #[test]
132 fn zero_size_top_is_zero() {
133 let mut obi = OrderBookImbalanceTop1::new();
134 assert_eq!(
135 obi.update(book(&[(100.0, 0.0)], &[(101.0, 0.0)])),
136 Some(0.0)
137 );
138 }
139
140 #[test]
141 fn empty_book_is_zero() {
142 let mut obi = OrderBookImbalanceTop1::new();
143 assert_eq!(
144 obi.update(OrderBook::new_unchecked(vec![], vec![])),
145 Some(0.0)
146 );
147 }
148
149 #[test]
150 fn batch_equals_streaming() {
151 let books: Vec<OrderBook> = (0..20)
152 .map(|i| {
153 let bid = 1.0 + f64::from(i % 5);
154 book(&[(100.0, bid)], &[(101.0, 2.0)])
155 })
156 .collect();
157 let mut a = OrderBookImbalanceTop1::new();
158 let mut b = OrderBookImbalanceTop1::new();
159 assert_eq!(
160 a.batch(&books),
161 books
162 .iter()
163 .map(|x| b.update(x.clone()))
164 .collect::<Vec<_>>()
165 );
166 }
167
168 #[test]
169 fn reset_clears_state() {
170 let mut obi = OrderBookImbalanceTop1::new();
171 obi.update(book(&[(100.0, 1.0)], &[(101.0, 1.0)]));
172 assert!(obi.is_ready());
173 obi.reset();
174 assert!(!obi.is_ready());
175 }
176}