drm_core/models/
orderbook.rs1use 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}