drm_core/models/
orderbook.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
6pub struct PriceLevel {
7    pub price: f64,
8    pub size: f64,
9}
10
11impl PriceLevel {
12    pub fn new(price: f64, size: f64) -> Self {
13        Self { price, size }
14    }
15}
16
17#[derive(Debug, Clone, Default, Serialize, Deserialize)]
18pub struct Orderbook {
19    pub market_id: String,
20    pub asset_id: String,
21    pub bids: Vec<PriceLevel>,
22    pub asks: Vec<PriceLevel>,
23    #[serde(default, skip_serializing_if = "Option::is_none")]
24    pub last_update_id: Option<u64>,
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub timestamp: Option<DateTime<Utc>>,
27}
28
29impl Orderbook {
30    pub fn best_bid(&self) -> Option<f64> {
31        self.bids.first().map(|l| l.price)
32    }
33
34    pub fn best_ask(&self) -> Option<f64> {
35        self.asks.first().map(|l| l.price)
36    }
37
38    pub fn mid_price(&self) -> Option<f64> {
39        match (self.best_bid(), self.best_ask()) {
40            (Some(bid), Some(ask)) => Some((bid + ask) / 2.0),
41            _ => None,
42        }
43    }
44
45    pub fn spread(&self) -> Option<f64> {
46        match (self.best_bid(), self.best_ask()) {
47            (Some(bid), Some(ask)) => Some(ask - bid),
48            _ => None,
49        }
50    }
51
52    pub fn has_data(&self) -> bool {
53        !self.bids.is_empty() && !self.asks.is_empty()
54    }
55
56    pub fn from_rest_response(
57        bids: &[RestPriceLevel],
58        asks: &[RestPriceLevel],
59        asset_id: impl Into<String>,
60    ) -> Self {
61        let mut parsed_bids: Vec<PriceLevel> = bids
62            .iter()
63            .filter_map(|b| {
64                let price = b.price.parse::<f64>().ok()?;
65                let size = b.size.parse::<f64>().ok()?;
66                if price > 0.0 && size > 0.0 {
67                    Some(PriceLevel::new(price, size))
68                } else {
69                    None
70                }
71            })
72            .collect();
73
74        let mut parsed_asks: Vec<PriceLevel> = asks
75            .iter()
76            .filter_map(|a| {
77                let price = a.price.parse::<f64>().ok()?;
78                let size = a.size.parse::<f64>().ok()?;
79                if price > 0.0 && size > 0.0 {
80                    Some(PriceLevel::new(price, size))
81                } else {
82                    None
83                }
84            })
85            .collect();
86
87        parsed_bids.sort_by(|a, b| {
88            b.price
89                .partial_cmp(&a.price)
90                .unwrap_or(std::cmp::Ordering::Equal)
91        });
92        parsed_asks.sort_by(|a, b| {
93            a.price
94                .partial_cmp(&b.price)
95                .unwrap_or(std::cmp::Ordering::Equal)
96        });
97
98        Self {
99            market_id: String::new(),
100            asset_id: asset_id.into(),
101            bids: parsed_bids,
102            asks: parsed_asks,
103            last_update_id: None,
104            timestamp: Some(Utc::now()),
105        }
106    }
107}
108
109#[derive(Debug, Clone, Deserialize)]
110pub struct RestPriceLevel {
111    pub price: String,
112    pub size: String,
113}
114
115#[derive(Debug, Default)]
116pub struct OrderbookManager {
117    orderbooks: HashMap<String, Orderbook>,
118}
119
120impl OrderbookManager {
121    pub fn new() -> Self {
122        Self {
123            orderbooks: HashMap::new(),
124        }
125    }
126
127    pub fn update(&mut self, token_id: impl Into<String>, orderbook: Orderbook) {
128        self.orderbooks.insert(token_id.into(), orderbook);
129    }
130
131    pub fn get(&self, token_id: &str) -> Option<&Orderbook> {
132        self.orderbooks.get(token_id)
133    }
134
135    pub fn get_best_bid_ask(&self, token_id: &str) -> (Option<f64>, Option<f64>) {
136        match self.get(token_id) {
137            Some(ob) => (ob.best_bid(), ob.best_ask()),
138            None => (None, None),
139        }
140    }
141
142    pub fn has_data(&self, token_id: &str) -> bool {
143        self.get(token_id).is_some_and(|ob| ob.has_data())
144    }
145
146    pub fn has_all_data(&self, token_ids: &[&str]) -> bool {
147        token_ids.iter().all(|id| self.has_data(id))
148    }
149
150    pub fn clear(&mut self) {
151        self.orderbooks.clear();
152    }
153
154    pub fn len(&self) -> usize {
155        self.orderbooks.len()
156    }
157
158    pub fn is_empty(&self) -> bool {
159        self.orderbooks.is_empty()
160    }
161
162    pub fn iter(&self) -> impl Iterator<Item = (&String, &Orderbook)> {
163        self.orderbooks.iter()
164    }
165}