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