nautilus_model/orderbook/
display.rs1use rust_decimal::Decimal;
19use tabled::{Table, Tabled, settings::Style};
20
21use super::{BookPrice, level::BookLevel, own::OwnBookLevel};
22use crate::orderbook::{OrderBook, own::OwnOrderBook};
23
24#[derive(Tabled)]
25struct BookLevelDisplay {
26 bids: String,
27 price: String,
28 asks: String,
29}
30
31#[must_use]
33pub(crate) fn pprint_book(
34 order_book: &OrderBook,
35 num_levels: usize,
36 group_size: Option<Decimal>,
37) -> String {
38 let data: Vec<BookLevelDisplay> = if let Some(group_size) = group_size {
39 let bid_quantities = order_book.group_bids(group_size, Some(num_levels));
40 let ask_quantities = order_book.group_asks(group_size, Some(num_levels));
41
42 let precision = group_size.scale();
44
45 let mut data = Vec::new();
46
47 for (price, qty) in ask_quantities.iter().rev() {
49 data.push(BookLevelDisplay {
50 bids: String::new(),
51 price: format!("{price:.precision$}", precision = precision as usize),
52 asks: qty.to_string(),
53 });
54 }
55
56 for (price, qty) in &bid_quantities {
58 data.push(BookLevelDisplay {
59 bids: qty.to_string(),
60 price: format!("{price:.precision$}", precision = precision as usize),
61 asks: String::new(),
62 });
63 }
64
65 data
66 } else {
67 let ask_levels: Vec<(&BookPrice, &BookLevel)> = order_book
68 .asks
69 .levels
70 .iter()
71 .take(num_levels)
72 .rev()
73 .collect();
74 let bid_levels: Vec<(&BookPrice, &BookLevel)> =
75 order_book.bids.levels.iter().take(num_levels).collect();
76 let levels: Vec<(&BookPrice, &BookLevel)> =
77 ask_levels.into_iter().chain(bid_levels).collect();
78
79 levels
80 .iter()
81 .map(|(book_price, level)| {
82 let is_bid_level = order_book.bids.levels.contains_key(book_price);
83 let is_ask_level = order_book.asks.levels.contains_key(book_price);
84
85 let bid_sizes: Vec<String> = level
86 .orders
87 .iter()
88 .filter(|_| is_bid_level)
89 .map(|order| format!("{}", order.1.size))
90 .collect();
91
92 let ask_sizes: Vec<String> = level
93 .orders
94 .iter()
95 .filter(|_| is_ask_level)
96 .map(|order| format!("{}", order.1.size))
97 .collect();
98
99 BookLevelDisplay {
100 bids: if bid_sizes.is_empty() {
101 String::new()
102 } else {
103 format!("[{}]", bid_sizes.join(", "))
104 },
105 price: format!("{}", level.price),
106 asks: if ask_sizes.is_empty() {
107 String::new()
108 } else {
109 format!("[{}]", ask_sizes.join(", "))
110 },
111 }
112 })
113 .collect()
114 };
115
116 let table = Table::new(data).with(Style::rounded()).to_string();
117
118 let header = format!(
119 "bid_levels: {}\nask_levels: {}",
120 order_book.bids.levels.len(),
121 order_book.asks.levels.len()
122 );
123
124 format!("{header}\n{table}")
125}
126
127#[must_use]
129pub(crate) fn pprint_own_book(
130 own_order_book: &OwnOrderBook,
131 num_levels: usize,
132 group_size: Option<Decimal>,
133) -> String {
134 let data: Vec<BookLevelDisplay> = if let Some(group_size) = group_size {
135 let bid_quantities =
136 own_order_book.bid_quantity(None, Some(num_levels), Some(group_size), None, None);
137 let ask_quantities =
138 own_order_book.ask_quantity(None, Some(num_levels), Some(group_size), None, None);
139
140 let precision = group_size.scale();
142
143 let mut data = Vec::new();
144
145 for (price, qty) in ask_quantities.iter().rev() {
147 data.push(BookLevelDisplay {
148 bids: String::new(),
149 price: format!("{price:.precision$}", precision = precision as usize),
150 asks: qty.to_string(),
151 });
152 }
153
154 for (price, qty) in &bid_quantities {
156 data.push(BookLevelDisplay {
157 bids: qty.to_string(),
158 price: format!("{price:.precision$}", precision = precision as usize),
159 asks: String::new(),
160 });
161 }
162
163 data
164 } else {
165 let ask_levels: Vec<(&BookPrice, &OwnBookLevel)> = own_order_book
166 .asks
167 .levels
168 .iter()
169 .take(num_levels)
170 .rev()
171 .collect();
172 let bid_levels: Vec<(&BookPrice, &OwnBookLevel)> =
173 own_order_book.bids.levels.iter().take(num_levels).collect();
174 let levels: Vec<(&BookPrice, &OwnBookLevel)> =
175 ask_levels.into_iter().chain(bid_levels).collect();
176
177 levels
178 .iter()
179 .map(|(book_price, level)| {
180 let is_bid_level = own_order_book.bids.levels.contains_key(book_price);
181 let is_ask_level = own_order_book.asks.levels.contains_key(book_price);
182
183 let bid_sizes: Vec<String> = level
184 .orders
185 .iter()
186 .filter(|_| is_bid_level)
187 .map(|order| format!("{}", order.1.size))
188 .collect();
189
190 let ask_sizes: Vec<String> = level
191 .orders
192 .iter()
193 .filter(|_| is_ask_level)
194 .map(|order| format!("{}", order.1.size))
195 .collect();
196
197 BookLevelDisplay {
198 bids: if bid_sizes.is_empty() {
199 String::new()
200 } else {
201 format!("[{}]", bid_sizes.join(", "))
202 },
203 price: format!("{}", level.price),
204 asks: if ask_sizes.is_empty() {
205 String::new()
206 } else {
207 format!("[{}]", ask_sizes.join(", "))
208 },
209 }
210 })
211 .collect()
212 };
213
214 let table = Table::new(data).with(Style::rounded()).to_string();
215
216 let header = format!(
217 "bid_levels: {}\nask_levels: {}",
218 own_order_book.bids.levels.len(),
219 own_order_book.asks.levels.len()
220 );
221
222 format!("{header}\n{table}")
223}