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)]
125#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
126pub struct PriceLevelChange {
127 pub side: PriceLevelSide,
129 pub price: FixedPrice,
131 pub size: f64,
133}
134
135pub type ChangeVec = SmallVec<[PriceLevelChange; 4]>;
138
139#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
141#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
142pub struct PriceLevel {
143 pub price: FixedPrice,
145 pub size: f64,
147}
148
149impl PriceLevel {
150 #[inline]
151 pub fn new(price: f64, size: f64) -> Self {
152 Self {
153 price: FixedPrice::from_f64(price),
154 size,
155 }
156 }
157
158 #[inline]
159 pub fn with_fixed(price: FixedPrice, size: f64) -> Self {
160 Self { price, size }
161 }
162}
163
164#[derive(Debug, Clone, Default, Serialize, Deserialize)]
166#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
167pub struct Orderbook {
168 pub asset_id: String,
170 pub bids: Vec<PriceLevel>,
172 pub asks: Vec<PriceLevel>,
174 #[serde(default, skip_serializing_if = "Option::is_none")]
176 pub last_update_id: Option<u64>,
177 #[serde(default, skip_serializing_if = "Option::is_none")]
179 pub timestamp: Option<DateTime<Utc>>,
180 #[serde(default, skip_serializing_if = "Option::is_none")]
182 pub hash: Option<String>,
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 asset_id: asset_id.into(),
259 bids: parsed_bids,
260 asks: parsed_asks,
261 last_update_id: None,
262 timestamp: Some(Utc::now()),
263 hash: None,
264 }
265 }
266}
267
268pub fn sort_bids(levels: &mut [PriceLevel]) {
271 levels.sort_unstable_by_key(|l| std::cmp::Reverse(l.price));
272}
273
274pub fn sort_asks(levels: &mut [PriceLevel]) {
277 levels.sort_unstable_by_key(|l| l.price);
278}
279
280#[inline]
285pub fn insert_bid(levels: &mut Vec<PriceLevel>, level: PriceLevel) {
286 let idx = levels.partition_point(|l| l.price > level.price);
287 levels.insert(idx, level);
288}
289
290#[inline]
293pub fn insert_ask(levels: &mut Vec<PriceLevel>, level: PriceLevel) {
294 let idx = levels.partition_point(|l| l.price < level.price);
295 levels.insert(idx, level);
296}
297
298#[inline]
304pub fn apply_bid_level(levels: &mut Vec<PriceLevel>, level: PriceLevel) {
305 match levels.binary_search_by(|l| level.price.cmp(&l.price)) {
306 Ok(idx) => {
307 if level.size > 0.0 {
308 levels[idx] = level;
309 } else {
310 levels.remove(idx);
311 }
312 }
313 Err(idx) => {
314 if level.size > 0.0 {
315 levels.insert(idx, level);
316 }
317 }
318 }
319}
320
321#[inline]
323pub fn apply_ask_level(levels: &mut Vec<PriceLevel>, level: PriceLevel) {
324 match levels.binary_search_by(|l| l.price.cmp(&level.price)) {
325 Ok(idx) => {
326 if level.size > 0.0 {
327 levels[idx] = level;
328 } else {
329 levels.remove(idx);
330 }
331 }
332 Err(idx) => {
333 if level.size > 0.0 {
334 levels.insert(idx, level);
335 }
336 }
337 }
338}
339
340#[inline]
347pub fn apply_bid_delta(levels: &mut Vec<PriceLevel>, fp: FixedPrice, delta: f64) -> f64 {
348 match levels.binary_search_by(|l| fp.cmp(&l.price)) {
349 Ok(idx) => {
350 let new_size = levels[idx].size + delta;
351 if new_size <= 0.0 {
352 levels.remove(idx);
353 0.0
354 } else {
355 levels[idx].size = new_size;
356 new_size
357 }
358 }
359 Err(idx) => {
360 if delta > 0.0 {
361 levels.insert(idx, PriceLevel::with_fixed(fp, delta));
362 delta
363 } else {
364 0.0
365 }
366 }
367 }
368}
369
370#[inline]
372pub fn apply_ask_delta(levels: &mut Vec<PriceLevel>, fp: FixedPrice, delta: f64) -> f64 {
373 match levels.binary_search_by(|l| l.price.cmp(&fp)) {
374 Ok(idx) => {
375 let new_size = levels[idx].size + delta;
376 if new_size <= 0.0 {
377 levels.remove(idx);
378 0.0
379 } else {
380 levels[idx].size = new_size;
381 new_size
382 }
383 }
384 Err(idx) => {
385 if delta > 0.0 {
386 levels.insert(idx, PriceLevel::with_fixed(fp, delta));
387 delta
388 } else {
389 0.0
390 }
391 }
392 }
393}
394
395#[derive(Debug, Clone, Deserialize)]
396pub struct RestPriceLevel {
397 pub price: String,
398 pub size: String,
399}