nautilus_model/orderbook/
display.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Posei Systems Pty Ltd. All rights reserved.
3//  https://poseitrader.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Functions related to order book display.
17
18use tabled::{Table, Tabled, settings::Style};
19
20use super::{
21    BookLevel, BookPrice,
22    own::{OwnBookLadder, OwnBookLevel},
23};
24use crate::orderbook::ladder::BookLadder;
25
26#[derive(Tabled)]
27struct BookLevelDisplay {
28    bids: String,
29    price: String,
30    asks: String,
31}
32
33/// Return a [`String`] representation of the order book in a human-readable table format.
34#[must_use]
35pub(crate) fn pprint_book(bids: &BookLadder, asks: &BookLadder, num_levels: usize) -> String {
36    let ask_levels: Vec<(&BookPrice, &BookLevel)> =
37        asks.levels.iter().take(num_levels).rev().collect();
38    let bid_levels: Vec<(&BookPrice, &BookLevel)> = bids.levels.iter().take(num_levels).collect();
39    let levels: Vec<(&BookPrice, &BookLevel)> = ask_levels.into_iter().chain(bid_levels).collect();
40
41    let data: Vec<BookLevelDisplay> = levels
42        .iter()
43        .map(|(book_price, level)| {
44            let is_bid_level = bids.levels.contains_key(book_price);
45            let is_ask_level = asks.levels.contains_key(book_price);
46
47            let bid_sizes: Vec<String> = level
48                .orders
49                .iter()
50                .filter(|_| is_bid_level)
51                .map(|order| format!("{}", order.1.size))
52                .collect();
53
54            let ask_sizes: Vec<String> = level
55                .orders
56                .iter()
57                .filter(|_| is_ask_level)
58                .map(|order| format!("{}", order.1.size))
59                .collect();
60
61            BookLevelDisplay {
62                bids: if bid_sizes.is_empty() {
63                    String::new()
64                } else {
65                    format!("[{}]", bid_sizes.join(", "))
66                },
67                price: format!("{}", level.price),
68                asks: if ask_sizes.is_empty() {
69                    String::new()
70                } else {
71                    format!("[{}]", ask_sizes.join(", "))
72                },
73            }
74        })
75        .collect();
76
77    let table = Table::new(data).with(Style::rounded()).to_string();
78
79    let header = format!(
80        "bid_levels: {}\nask_levels: {}",
81        bids.levels.len(),
82        asks.levels.len()
83    );
84
85    format!("{}\n{}", header, table)
86}
87
88// TODO: Probably consolidate the below at some point
89/// Return a [`String`] representation of the own order book in a human-readable table format.
90#[must_use]
91pub(crate) fn pprint_own_book(
92    bids: &OwnBookLadder,
93    asks: &OwnBookLadder,
94    num_levels: usize,
95) -> String {
96    let ask_levels: Vec<(&BookPrice, &OwnBookLevel)> =
97        asks.levels.iter().take(num_levels).rev().collect();
98    let bid_levels: Vec<(&BookPrice, &OwnBookLevel)> =
99        bids.levels.iter().take(num_levels).collect();
100    let levels: Vec<(&BookPrice, &OwnBookLevel)> =
101        ask_levels.into_iter().chain(bid_levels).collect();
102
103    let data: Vec<BookLevelDisplay> = levels
104        .iter()
105        .map(|(book_price, level)| {
106            let is_bid_level = bids.levels.contains_key(book_price);
107            let is_ask_level = asks.levels.contains_key(book_price);
108
109            let bid_sizes: Vec<String> = level
110                .orders
111                .iter()
112                .filter(|_| is_bid_level)
113                .map(|order| format!("{}", order.1.size))
114                .collect();
115
116            let ask_sizes: Vec<String> = level
117                .orders
118                .iter()
119                .filter(|_| is_ask_level)
120                .map(|order| format!("{}", order.1.size))
121                .collect();
122
123            BookLevelDisplay {
124                bids: if bid_sizes.is_empty() {
125                    String::new()
126                } else {
127                    format!("[{}]", bid_sizes.join(", "))
128                },
129                price: format!("{}", level.price),
130                asks: if ask_sizes.is_empty() {
131                    String::new()
132                } else {
133                    format!("[{}]", ask_sizes.join(", "))
134                },
135            }
136        })
137        .collect();
138
139    let table = Table::new(data).with(Style::rounded()).to_string();
140
141    let header = format!(
142        "bid_levels: {}\nask_levels: {}",
143        bids.levels.len(),
144        asks.levels.len()
145    );
146
147    format!("{}\n{}", header, table)
148}