Skip to main content

matchcore/orderbook/analytics/
depth_statistics.rs

1use crate::{Level2, LimitBook, Notional, Price, Quantity, Side};
2
3/// Represents the depth statistics of the order book
4#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5#[derive(Debug, Clone, PartialEq)]
6pub struct DepthStatistics {
7    /// Number of price levels analyzed
8    n_analyzed_levels: usize,
9
10    /// Total value across all analyzed price levels
11    total_value: Notional,
12
13    /// Total size across all analyzed price levels
14    total_size: Quantity,
15
16    /// Average price level size
17    average_level_size: f64,
18
19    /// Smallest price level size
20    min_level_size: Quantity,
21
22    /// Largest price level size
23    max_level_size: Quantity,
24
25    /// Standard deviation of price level sizes
26    std_dev_level_size: f64,
27}
28
29impl DepthStatistics {
30    /// Compute the depth statistics of price levels (0 n_levels means all levels)
31    pub(super) fn compute(book: &LimitBook, side: Side, n_levels: usize) -> Self {
32        let mut stats = Self {
33            n_analyzed_levels: 0,
34            total_value: Notional(0),
35            total_size: Quantity(0),
36            average_level_size: 0.0,
37            min_level_size: Quantity(u64::MAX),
38            max_level_size: Quantity(0),
39            std_dev_level_size: 0.0,
40        };
41
42        let n_levels = if n_levels == 0 { usize::MAX } else { n_levels };
43        let mut sizes = Vec::new();
44
45        match side {
46            Side::Buy => {
47                for (price, level_id) in book.bids.iter().rev().take(n_levels) {
48                    let level = &book.levels[*level_id];
49                    stats.observe_level(*price, level.total_quantity());
50                    sizes.push(level.total_quantity());
51                }
52            }
53            Side::Sell => {
54                for (price, level_id) in book.asks.iter().take(n_levels) {
55                    let level = &book.levels[*level_id];
56                    stats.observe_level(*price, level.total_quantity());
57                    sizes.push(level.total_quantity());
58                }
59            }
60        }
61
62        if stats.is_empty() {
63            stats.min_level_size = Quantity(0);
64            return stats;
65        }
66
67        stats.average_level_size = stats.total_size.as_f64() / stats.n_analyzed_levels as f64;
68
69        let variance = sizes
70            .iter()
71            .map(|size| (size.as_f64() - stats.average_level_size).powi(2))
72            .sum::<f64>()
73            / stats.n_analyzed_levels as f64;
74        stats.std_dev_level_size = variance.sqrt();
75
76        stats
77    }
78
79    /// Compute the depth statistics of price levels (0 n_levels means all levels)
80    /// from a level 2 market data snapshot
81    pub(crate) fn compute_from_level2(level2: &Level2, side: Side, n_levels: usize) -> Self {
82        let mut stats = Self {
83            n_analyzed_levels: 0,
84            total_value: Notional(0),
85            total_size: Quantity(0),
86            average_level_size: 0.0,
87            min_level_size: Quantity(u64::MAX),
88            max_level_size: Quantity(0),
89            std_dev_level_size: 0.0,
90        };
91
92        let n_levels = if n_levels == 0 { usize::MAX } else { n_levels };
93        let mut sizes = Vec::new();
94
95        match side {
96            Side::Buy => {
97                for (price, quantity) in level2.bid_levels().iter().take(n_levels) {
98                    stats.observe_level(*price, *quantity);
99                    sizes.push(*quantity);
100                }
101            }
102            Side::Sell => {
103                for (price, quantity) in level2.ask_levels().iter().take(n_levels) {
104                    stats.observe_level(*price, *quantity);
105                    sizes.push(*quantity);
106                }
107            }
108        }
109
110        if stats.is_empty() {
111            stats.min_level_size = Quantity(0);
112            return stats;
113        }
114
115        stats.average_level_size = stats.total_size.as_f64() / stats.n_analyzed_levels as f64;
116
117        let variance = sizes
118            .iter()
119            .map(|size| (size.as_f64() - stats.average_level_size).powi(2))
120            .sum::<f64>()
121            / stats.n_analyzed_levels as f64;
122        stats.std_dev_level_size = variance.sqrt();
123
124        stats
125    }
126
127    /// Observe a price level and update the statistics
128    fn observe_level(&mut self, price: Price, quantity: Quantity) {
129        self.n_analyzed_levels += 1;
130        self.total_value = self.total_value.saturating_add(price * quantity);
131        self.total_size = self.total_size.saturating_add(quantity);
132        self.min_level_size = self.min_level_size.min(quantity);
133        self.max_level_size = self.max_level_size.max(quantity);
134    }
135
136    /// Check if the statistics are empty
137    pub fn is_empty(&self) -> bool {
138        self.n_analyzed_levels == 0
139    }
140
141    /// Get the number of analyzed price levels
142    pub fn n_analyzed_levels(&self) -> usize {
143        self.n_analyzed_levels
144    }
145
146    /// Get the total value of all analyzed price levels
147    pub fn total_value(&self) -> Notional {
148        self.total_value
149    }
150
151    /// Get the total size of all analyzed price levels
152    pub fn total_size(&self) -> Quantity {
153        self.total_size
154    }
155
156    /// Get the average size of all analyzed price levels
157    pub fn average_level_size(&self) -> f64 {
158        self.average_level_size
159    }
160
161    /// Get the smallest size of all analyzed price levels
162    pub fn min_level_size(&self) -> Quantity {
163        self.min_level_size
164    }
165
166    /// Get the largest size of all analyzed price levels
167    pub fn max_level_size(&self) -> Quantity {
168        self.max_level_size
169    }
170
171    /// Get the standard deviation of the size of all analyzed price levels
172    pub fn std_dev_level_size(&self) -> f64 {
173        self.std_dev_level_size
174    }
175
176    /// Get the volume-weighted average price of all analyzed price levels
177    pub fn vwap(&self) -> f64 {
178        self.total_value / self.total_size
179    }
180}