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