1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use smallvec::SmallVec;
4
5#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct FixedPrice(u64);
16
17impl FixedPrice {
18 pub const SCALE: u64 = 10_000;
19 pub const ZERO: Self = Self(0);
20 pub const ONE: Self = Self(Self::SCALE);
21
22 #[inline]
23 pub fn from_f64(price: f64) -> Self {
24 Self((price * Self::SCALE as f64).round() as u64)
25 }
26
27 #[inline]
28 pub fn to_f64(self) -> f64 {
29 self.0 as f64 / Self::SCALE as f64
30 }
31
32 #[inline]
33 pub fn raw(self) -> u64 {
34 self.0
35 }
36
37 #[inline]
38 pub fn from_raw(raw: u64) -> Self {
39 Self(raw)
40 }
41
42 #[inline]
44 pub fn complement(self) -> Self {
45 Self(Self::SCALE.saturating_sub(self.0))
46 }
47
48 #[inline]
49 pub fn midpoint(self, other: Self) -> Self {
50 Self((self.0 + other.0) / 2)
51 }
52}
53
54impl std::fmt::Debug for FixedPrice {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 write!(f, "FixedPrice({})", self.to_f64())
57 }
58}
59
60impl std::fmt::Display for FixedPrice {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 write!(f, "{}", self.to_f64())
63 }
64}
65
66impl Default for FixedPrice {
67 fn default() -> Self {
68 Self::ZERO
69 }
70}
71
72impl From<f64> for FixedPrice {
73 #[inline]
74 fn from(v: f64) -> Self {
75 Self::from_f64(v)
76 }
77}
78
79impl From<FixedPrice> for f64 {
80 #[inline]
81 fn from(v: FixedPrice) -> Self {
82 v.to_f64()
83 }
84}
85
86impl Serialize for FixedPrice {
87 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
88 serializer.serialize_f64(self.to_f64())
89 }
90}
91
92impl<'de> Deserialize<'de> for FixedPrice {
93 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
94 let v = f64::deserialize(deserializer)?;
95 Ok(Self::from_f64(v))
96 }
97}
98
99#[cfg(feature = "schema")]
100impl schemars::JsonSchema for FixedPrice {
101 fn schema_name() -> String {
102 "number".to_string()
103 }
104
105 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
106 f64::json_schema(gen)
107 }
108}
109
110#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
116#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
117#[serde(rename_all = "snake_case")]
118pub enum PriceLevelSide {
119 Bid,
120 Ask,
121}
122
123#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
126#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
127pub struct PriceLevelChange {
128 pub side: PriceLevelSide,
129 pub price: FixedPrice,
130 pub size: f64,
131}
132
133pub type ChangeVec = SmallVec<[PriceLevelChange; 4]>;
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub enum OrderbookUpdate {
140 Snapshot(Orderbook),
142 Delta {
145 changes: ChangeVec,
146 timestamp: Option<DateTime<Utc>>,
147 },
148}
149
150#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
151#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
152pub struct PriceLevel {
153 pub price: FixedPrice,
154 pub size: f64,
155}
156
157impl PriceLevel {
158 #[inline]
159 pub fn new(price: f64, size: f64) -> Self {
160 Self {
161 price: FixedPrice::from_f64(price),
162 size,
163 }
164 }
165
166 #[inline]
167 pub fn with_fixed(price: FixedPrice, size: f64) -> Self {
168 Self { price, size }
169 }
170}
171
172#[derive(Debug, Clone, Default, Serialize, Deserialize)]
173#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
174pub struct Orderbook {
175 pub market_id: String,
176 pub asset_id: String,
177 pub bids: Vec<PriceLevel>,
178 pub asks: Vec<PriceLevel>,
179 #[serde(default, skip_serializing_if = "Option::is_none")]
180 pub last_update_id: Option<u64>,
181 #[serde(default, skip_serializing_if = "Option::is_none")]
182 pub timestamp: Option<DateTime<Utc>>,
183}
184
185impl Orderbook {
186 #[inline]
187 pub fn best_bid(&self) -> Option<f64> {
188 self.bids.first().map(|l| l.price.to_f64())
189 }
190
191 #[inline]
192 pub fn best_ask(&self) -> Option<f64> {
193 self.asks.first().map(|l| l.price.to_f64())
194 }
195
196 #[inline]
197 pub fn mid_price(&self) -> Option<f64> {
198 match (self.bids.first(), self.asks.first()) {
199 (Some(bid), Some(ask)) => Some(bid.price.midpoint(ask.price).to_f64()),
200 _ => None,
201 }
202 }
203
204 #[inline]
205 pub fn spread(&self) -> Option<f64> {
206 match (self.bids.first(), self.asks.first()) {
207 (Some(bid), Some(ask)) => Some(ask.price.to_f64() - bid.price.to_f64()),
208 _ => None,
209 }
210 }
211
212 #[inline]
213 pub fn has_data(&self) -> bool {
214 !self.bids.is_empty() && !self.asks.is_empty()
215 }
216
217 pub fn sort(&mut self) {
219 sort_bids(&mut self.bids);
220 sort_asks(&mut self.asks);
221 }
222
223 pub fn from_rest_response(
224 bids: &[RestPriceLevel],
225 asks: &[RestPriceLevel],
226 asset_id: impl Into<String>,
227 ) -> Self {
228 let mut parsed_bids: Vec<PriceLevel> = bids
229 .iter()
230 .filter_map(|b| {
231 let price = b.price.parse::<f64>().ok()?;
232 let size = b.size.parse::<f64>().ok()?;
233 if price > 0.0 && size > 0.0 {
234 Some(PriceLevel::new(price, size))
235 } else {
236 None
237 }
238 })
239 .collect();
240
241 let mut parsed_asks: Vec<PriceLevel> = asks
242 .iter()
243 .filter_map(|a| {
244 let price = a.price.parse::<f64>().ok()?;
245 let size = a.size.parse::<f64>().ok()?;
246 if price > 0.0 && size > 0.0 {
247 Some(PriceLevel::new(price, size))
248 } else {
249 None
250 }
251 })
252 .collect();
253
254 sort_bids(&mut parsed_bids);
255 sort_asks(&mut parsed_asks);
256
257 Self {
258 market_id: String::new(),
259 asset_id: asset_id.into(),
260 bids: parsed_bids,
261 asks: parsed_asks,
262 last_update_id: None,
263 timestamp: Some(Utc::now()),
264 }
265 }
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize)]
270#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
271pub struct OrderbookSnapshot {
272 pub timestamp: DateTime<Utc>,
273 #[serde(default, skip_serializing_if = "Option::is_none")]
274 pub recorded_at: Option<DateTime<Utc>>,
275 #[serde(default, skip_serializing_if = "Option::is_none")]
276 pub hash: Option<String>,
277 pub bids: Vec<PriceLevel>,
278 pub asks: Vec<PriceLevel>,
279}
280
281pub fn sort_bids(levels: &mut [PriceLevel]) {
284 levels.sort_unstable_by(|a, b| b.price.cmp(&a.price));
285}
286
287pub fn sort_asks(levels: &mut [PriceLevel]) {
290 levels.sort_unstable_by(|a, b| a.price.cmp(&b.price));
291}
292
293#[inline]
298pub fn insert_bid(levels: &mut Vec<PriceLevel>, level: PriceLevel) {
299 levels.push(level);
300 sort_bids(levels);
301}
302
303#[inline]
306pub fn insert_ask(levels: &mut Vec<PriceLevel>, level: PriceLevel) {
307 levels.push(level);
308 sort_asks(levels);
309}
310
311#[derive(Debug, Clone, Deserialize)]
312pub struct RestPriceLevel {
313 pub price: String,
314 pub size: String,
315}