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