nautilus_indicators/book/
imbalance.rs1use std::fmt::Display;
17
18use nautilus_model::{orderbook::OrderBook, types::Quantity};
19
20use crate::indicator::Indicator;
21
22#[repr(C)]
23#[derive(Debug, Default)]
24#[cfg_attr(
25 feature = "python",
26 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.indicators")
27)]
28pub struct BookImbalanceRatio {
29 pub value: f64,
30 pub count: usize,
31 pub initialized: bool,
32 has_inputs: bool,
33}
34
35impl Display for BookImbalanceRatio {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 write!(f, "{}()", self.name())
38 }
39}
40
41impl Indicator for BookImbalanceRatio {
42 fn name(&self) -> String {
43 stringify!(BookImbalanceRatio).to_string()
44 }
45
46 fn has_inputs(&self) -> bool {
47 self.has_inputs
48 }
49
50 fn initialized(&self) -> bool {
51 self.initialized
52 }
53
54 fn handle_book(&mut self, book: &OrderBook) {
55 self.update(book.best_bid_size(), book.best_ask_size());
56 }
57
58 fn reset(&mut self) {
59 self.value = 0.0;
60 self.count = 0;
61 self.has_inputs = false;
62 self.initialized = false;
63 }
64}
65
66impl BookImbalanceRatio {
67 #[must_use]
69 pub const fn new() -> Self {
70 Self {
71 value: 0.0,
72 count: 0,
73 has_inputs: false,
74 initialized: false,
75 }
76 }
77
78 pub fn update(&mut self, best_bid: Option<Quantity>, best_ask: Option<Quantity>) {
79 self.has_inputs = true;
80 self.count += 1;
81
82 if let (Some(best_bid), Some(best_ask)) = (best_bid, best_ask) {
83 let smaller = std::cmp::min(best_bid, best_ask);
84 let larger = std::cmp::max(best_bid, best_ask);
85
86 let ratio = smaller.as_f64() / larger.as_f64();
87 self.value = ratio;
88
89 self.initialized = true;
90 }
91 }
93}
94
95#[cfg(test)]
99mod tests {
100 use nautilus_model::{
101 identifiers::InstrumentId,
102 stubs::{stub_order_book_mbp, stub_order_book_mbp_appl_xnas},
103 };
104 use rstest::rstest;
105
106 use super::*;
107
108 #[rstest]
109 fn test_initialized() {
110 let imbalance = BookImbalanceRatio::new();
111 let display_str = format!("{imbalance}");
112 assert_eq!(display_str, "BookImbalanceRatio()");
113 assert_eq!(imbalance.value, 0.0);
114 assert_eq!(imbalance.count, 0);
115 assert!(!imbalance.has_inputs);
116 assert!(!imbalance.initialized);
117 }
118
119 #[rstest]
120 fn test_one_value_input_balanced() {
121 let mut imbalance = BookImbalanceRatio::new();
122 let book = stub_order_book_mbp_appl_xnas();
123 imbalance.handle_book(&book);
124
125 assert_eq!(imbalance.count, 1);
126 assert_eq!(imbalance.value, 1.0);
127 assert!(imbalance.initialized);
128 assert!(imbalance.has_inputs);
129 }
130
131 #[rstest]
132 fn test_reset() {
133 let mut imbalance = BookImbalanceRatio::new();
134 let book = stub_order_book_mbp_appl_xnas();
135 imbalance.handle_book(&book);
136 imbalance.reset();
137
138 assert_eq!(imbalance.count, 0);
139 assert_eq!(imbalance.value, 0.0);
140 assert!(!imbalance.initialized);
141 assert!(!imbalance.has_inputs);
142 }
143
144 #[rstest]
145 fn test_one_value_input_with_bid_imbalance() {
146 let mut imbalance = BookImbalanceRatio::new();
147 let book = stub_order_book_mbp(
148 InstrumentId::from("AAPL.XNAS"),
149 101.0,
150 100.0,
151 200.0, 100.0,
153 2,
154 0.01,
155 0,
156 100.0,
157 10,
158 );
159 imbalance.handle_book(&book);
160
161 assert_eq!(imbalance.count, 1);
162 assert_eq!(imbalance.value, 0.5);
163 assert!(imbalance.initialized);
164 assert!(imbalance.has_inputs);
165 }
166
167 #[rstest]
168 fn test_one_value_input_with_ask_imbalance() {
169 let mut imbalance = BookImbalanceRatio::new();
170 let book = stub_order_book_mbp(
171 InstrumentId::from("AAPL.XNAS"),
172 101.0,
173 100.0,
174 100.0,
175 200.0, 2,
177 0.01,
178 0,
179 100.0,
180 10,
181 );
182 imbalance.handle_book(&book);
183
184 assert_eq!(imbalance.count, 1);
185 assert_eq!(imbalance.value, 0.5);
186 assert!(imbalance.initialized);
187 assert!(imbalance.has_inputs);
188 }
189
190 #[rstest]
191 fn test_one_value_input_with_bid_imbalance_multiple_inputs() {
192 let mut imbalance = BookImbalanceRatio::new();
193 let book = stub_order_book_mbp(
194 InstrumentId::from("AAPL.XNAS"),
195 101.0,
196 100.0,
197 200.0, 100.0,
199 2,
200 0.01,
201 0,
202 100.0,
203 10,
204 );
205 imbalance.handle_book(&book);
206 imbalance.handle_book(&book);
207 imbalance.handle_book(&book);
208
209 assert_eq!(imbalance.count, 3);
210 assert_eq!(imbalance.value, 0.5);
211 assert!(imbalance.initialized);
212 assert!(imbalance.has_inputs);
213 }
214}