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)]
96mod tests {
97 use nautilus_model::{
98 identifiers::InstrumentId,
99 stubs::{stub_order_book_mbp, stub_order_book_mbp_appl_xnas},
100 };
101 use rstest::rstest;
102
103 use super::*;
104
105 #[rstest]
106 fn test_initialized() {
107 let imbalance = BookImbalanceRatio::new();
108 let display_str = format!("{imbalance}");
109 assert_eq!(display_str, "BookImbalanceRatio()");
110 assert_eq!(imbalance.value, 0.0);
111 assert_eq!(imbalance.count, 0);
112 assert!(!imbalance.has_inputs);
113 assert!(!imbalance.initialized);
114 }
115
116 #[rstest]
117 fn test_one_value_input_balanced() {
118 let mut imbalance = BookImbalanceRatio::new();
119 let book = stub_order_book_mbp_appl_xnas();
120 imbalance.handle_book(&book);
121
122 assert_eq!(imbalance.count, 1);
123 assert_eq!(imbalance.value, 1.0);
124 assert!(imbalance.initialized);
125 assert!(imbalance.has_inputs);
126 }
127
128 #[rstest]
129 fn test_reset() {
130 let mut imbalance = BookImbalanceRatio::new();
131 let book = stub_order_book_mbp_appl_xnas();
132 imbalance.handle_book(&book);
133 imbalance.reset();
134
135 assert_eq!(imbalance.count, 0);
136 assert_eq!(imbalance.value, 0.0);
137 assert!(!imbalance.initialized);
138 assert!(!imbalance.has_inputs);
139 }
140
141 #[rstest]
142 fn test_one_value_input_with_bid_imbalance() {
143 let mut imbalance = BookImbalanceRatio::new();
144 let book = stub_order_book_mbp(
145 InstrumentId::from("AAPL.XNAS"),
146 101.0,
147 100.0,
148 200.0, 100.0,
150 2,
151 0.01,
152 0,
153 100.0,
154 10,
155 );
156 imbalance.handle_book(&book);
157
158 assert_eq!(imbalance.count, 1);
159 assert_eq!(imbalance.value, 0.5);
160 assert!(imbalance.initialized);
161 assert!(imbalance.has_inputs);
162 }
163
164 #[rstest]
165 fn test_one_value_input_with_ask_imbalance() {
166 let mut imbalance = BookImbalanceRatio::new();
167 let book = stub_order_book_mbp(
168 InstrumentId::from("AAPL.XNAS"),
169 101.0,
170 100.0,
171 100.0,
172 200.0, 2,
174 0.01,
175 0,
176 100.0,
177 10,
178 );
179 imbalance.handle_book(&book);
180
181 assert_eq!(imbalance.count, 1);
182 assert_eq!(imbalance.value, 0.5);
183 assert!(imbalance.initialized);
184 assert!(imbalance.has_inputs);
185 }
186
187 #[rstest]
188 fn test_one_value_input_with_bid_imbalance_multiple_inputs() {
189 let mut imbalance = BookImbalanceRatio::new();
190 let book = stub_order_book_mbp(
191 InstrumentId::from("AAPL.XNAS"),
192 101.0,
193 100.0,
194 200.0, 100.0,
196 2,
197 0.01,
198 0,
199 100.0,
200 10,
201 );
202 imbalance.handle_book(&book);
203 imbalance.handle_book(&book);
204 imbalance.handle_book(&book);
205
206 assert_eq!(imbalance.count, 3);
207 assert_eq!(imbalance.value, 0.5);
208 assert!(imbalance.initialized);
209 assert!(imbalance.has_inputs);
210 }
211}