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 Reconnected,
151}
152
153#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
154#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
155pub struct PriceLevel {
156 pub price: FixedPrice,
157 pub size: f64,
158}
159
160impl PriceLevel {
161 #[inline]
162 pub fn new(price: f64, size: f64) -> Self {
163 Self {
164 price: FixedPrice::from_f64(price),
165 size,
166 }
167 }
168
169 #[inline]
170 pub fn with_fixed(price: FixedPrice, size: f64) -> Self {
171 Self { price, size }
172 }
173}
174
175#[derive(Debug, Clone, Default, Serialize, Deserialize)]
176#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
177pub struct Orderbook {
178 pub market_id: String,
179 pub asset_id: String,
180 pub bids: Vec<PriceLevel>,
181 pub asks: Vec<PriceLevel>,
182 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub last_update_id: Option<u64>,
184 #[serde(default, skip_serializing_if = "Option::is_none")]
185 pub timestamp: Option<DateTime<Utc>>,
186 #[serde(default, skip_serializing_if = "Option::is_none")]
189 pub hash: Option<String>,
190}
191
192impl Orderbook {
193 #[inline]
194 pub fn best_bid(&self) -> Option<f64> {
195 self.bids.first().map(|l| l.price.to_f64())
196 }
197
198 #[inline]
199 pub fn best_ask(&self) -> Option<f64> {
200 self.asks.first().map(|l| l.price.to_f64())
201 }
202
203 #[inline]
204 pub fn mid_price(&self) -> Option<f64> {
205 match (self.bids.first(), self.asks.first()) {
206 (Some(bid), Some(ask)) => Some(bid.price.midpoint(ask.price).to_f64()),
207 _ => None,
208 }
209 }
210
211 #[inline]
212 pub fn spread(&self) -> Option<f64> {
213 match (self.bids.first(), self.asks.first()) {
214 (Some(bid), Some(ask)) => Some(ask.price.to_f64() - bid.price.to_f64()),
215 _ => None,
216 }
217 }
218
219 #[inline]
220 pub fn has_data(&self) -> bool {
221 !self.bids.is_empty() && !self.asks.is_empty()
222 }
223
224 pub fn sort(&mut self) {
226 sort_bids(&mut self.bids);
227 sort_asks(&mut self.asks);
228 }
229
230 pub fn from_rest_response(
231 bids: &[RestPriceLevel],
232 asks: &[RestPriceLevel],
233 asset_id: impl Into<String>,
234 ) -> Self {
235 let mut parsed_bids: Vec<PriceLevel> = bids
236 .iter()
237 .filter_map(|b| {
238 let price = b.price.parse::<f64>().ok()?;
239 let size = b.size.parse::<f64>().ok()?;
240 if price > 0.0 && size > 0.0 {
241 Some(PriceLevel::new(price, size))
242 } else {
243 None
244 }
245 })
246 .collect();
247
248 let mut parsed_asks: Vec<PriceLevel> = asks
249 .iter()
250 .filter_map(|a| {
251 let price = a.price.parse::<f64>().ok()?;
252 let size = a.size.parse::<f64>().ok()?;
253 if price > 0.0 && size > 0.0 {
254 Some(PriceLevel::new(price, size))
255 } else {
256 None
257 }
258 })
259 .collect();
260
261 sort_bids(&mut parsed_bids);
262 sort_asks(&mut parsed_asks);
263
264 Self {
265 market_id: String::new(),
266 asset_id: asset_id.into(),
267 bids: parsed_bids,
268 asks: parsed_asks,
269 last_update_id: None,
270 timestamp: Some(Utc::now()),
271 hash: None,
272 }
273 }
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
279pub struct OrderbookSnapshot {
280 pub timestamp: DateTime<Utc>,
281 #[serde(default, skip_serializing_if = "Option::is_none")]
282 pub recorded_at: Option<DateTime<Utc>>,
283 #[serde(default, skip_serializing_if = "Option::is_none")]
284 pub hash: Option<String>,
285 pub bids: Vec<PriceLevel>,
286 pub asks: Vec<PriceLevel>,
287}
288
289pub fn sort_bids(levels: &mut [PriceLevel]) {
292 levels.sort_unstable_by(|a, b| b.price.cmp(&a.price));
293}
294
295pub fn sort_asks(levels: &mut [PriceLevel]) {
298 levels.sort_unstable_by(|a, b| a.price.cmp(&b.price));
299}
300
301#[inline]
306pub fn insert_bid(levels: &mut Vec<PriceLevel>, level: PriceLevel) {
307 levels.push(level);
308 sort_bids(levels);
309}
310
311#[inline]
314pub fn insert_ask(levels: &mut Vec<PriceLevel>, level: PriceLevel) {
315 levels.push(level);
316 sort_asks(levels);
317}
318
319#[derive(Debug, Clone, Deserialize)]
320pub struct RestPriceLevel {
321 pub price: String,
322 pub size: String,
323}