nautilus_model/orderbook/
display.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.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    Table::new(data).with(Style::rounded()).to_string()
78}
79
80// TODO: Probably consolidate the below at some point
81/// Return a [`String`] representation of the own order book in a human-readable table format.
82#[must_use]
83pub(crate) fn pprint_own_book(
84    bids: &OwnBookLadder,
85    asks: &OwnBookLadder,
86    num_levels: usize,
87) -> String {
88    let ask_levels: Vec<(&BookPrice, &OwnBookLevel)> =
89        asks.levels.iter().take(num_levels).rev().collect();
90    let bid_levels: Vec<(&BookPrice, &OwnBookLevel)> =
91        bids.levels.iter().take(num_levels).collect();
92    let levels: Vec<(&BookPrice, &OwnBookLevel)> =
93        ask_levels.into_iter().chain(bid_levels).collect();
94
95    let data: Vec<BookLevelDisplay> = levels
96        .iter()
97        .map(|(book_price, level)| {
98            let is_bid_level = bids.levels.contains_key(book_price);
99            let is_ask_level = asks.levels.contains_key(book_price);
100
101            let bid_sizes: Vec<String> = level
102                .orders
103                .iter()
104                .filter(|_| is_bid_level)
105                .map(|order| format!("{}", order.1.size))
106                .collect();
107
108            let ask_sizes: Vec<String> = level
109                .orders
110                .iter()
111                .filter(|_| is_ask_level)
112                .map(|order| format!("{}", order.1.size))
113                .collect();
114
115            BookLevelDisplay {
116                bids: if bid_sizes.is_empty() {
117                    String::new()
118                } else {
119                    format!("[{}]", bid_sizes.join(", "))
120                },
121                price: format!("{}", level.price),
122                asks: if ask_sizes.is_empty() {
123                    String::new()
124                } else {
125                    format!("[{}]", ask_sizes.join(", "))
126                },
127            }
128        })
129        .collect();
130
131    Table::new(data).with(Style::rounded()).to_string()
132}