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, Copy, Serialize, Deserialize)]
138#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
139pub struct PriceLevel {
140 pub price: FixedPrice,
141 pub size: f64,
142}
143
144impl PriceLevel {
145 #[inline]
146 pub fn new(price: f64, size: f64) -> Self {
147 Self {
148 price: FixedPrice::from_f64(price),
149 size,
150 }
151 }
152
153 #[inline]
154 pub fn with_fixed(price: FixedPrice, size: f64) -> Self {
155 Self { price, size }
156 }
157}
158
159#[derive(Debug, Clone, Default, Serialize, Deserialize)]
160#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
161pub struct Orderbook {
162 pub market_id: String,
163 pub asset_id: String,
164 pub bids: Vec<PriceLevel>,
165 pub asks: Vec<PriceLevel>,
166 #[serde(default, skip_serializing_if = "Option::is_none")]
167 pub last_update_id: Option<u64>,
168 #[serde(default, skip_serializing_if = "Option::is_none")]
169 pub timestamp: Option<DateTime<Utc>>,
170 #[serde(default, skip_serializing_if = "Option::is_none")]
173 pub hash: Option<String>,
174}
175
176impl Orderbook {
177 #[inline]
178 pub fn best_bid(&self) -> Option<f64> {
179 self.bids.first().map(|l| l.price.to_f64())
180 }
181
182 #[inline]
183 pub fn best_ask(&self) -> Option<f64> {
184 self.asks.first().map(|l| l.price.to_f64())
185 }
186
187 #[inline]
188 pub fn mid_price(&self) -> Option<f64> {
189 match (self.bids.first(), self.asks.first()) {
190 (Some(bid), Some(ask)) => Some(bid.price.midpoint(ask.price).to_f64()),
191 _ => None,
192 }
193 }
194
195 #[inline]
196 pub fn spread(&self) -> Option<f64> {
197 match (self.bids.first(), self.asks.first()) {
198 (Some(bid), Some(ask)) => Some(ask.price.to_f64() - bid.price.to_f64()),
199 _ => None,
200 }
201 }
202
203 #[inline]
204 pub fn has_data(&self) -> bool {
205 !self.bids.is_empty() && !self.asks.is_empty()
206 }
207
208 pub fn sort(&mut self) {
210 sort_bids(&mut self.bids);
211 sort_asks(&mut self.asks);
212 }
213
214 pub fn from_rest_response(
215 bids: &[RestPriceLevel],
216 asks: &[RestPriceLevel],
217 asset_id: impl Into<String>,
218 ) -> Self {
219 let mut parsed_bids: Vec<PriceLevel> = bids
220 .iter()
221 .filter_map(|b| {
222 let price = b.price.parse::<f64>().ok()?;
223 let size = b.size.parse::<f64>().ok()?;
224 if price > 0.0 && size > 0.0 {
225 Some(PriceLevel::new(price, size))
226 } else {
227 None
228 }
229 })
230 .collect();
231
232 let mut parsed_asks: Vec<PriceLevel> = asks
233 .iter()
234 .filter_map(|a| {
235 let price = a.price.parse::<f64>().ok()?;
236 let size = a.size.parse::<f64>().ok()?;
237 if price > 0.0 && size > 0.0 {
238 Some(PriceLevel::new(price, size))
239 } else {
240 None
241 }
242 })
243 .collect();
244
245 sort_bids(&mut parsed_bids);
246 sort_asks(&mut parsed_asks);
247
248 Self {
249 market_id: String::new(),
250 asset_id: asset_id.into(),
251 bids: parsed_bids,
252 asks: parsed_asks,
253 last_update_id: None,
254 timestamp: Some(Utc::now()),
255 hash: None,
256 }
257 }
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
262#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
263pub struct OrderbookSnapshot {
264 pub timestamp: DateTime<Utc>,
265 #[serde(default, skip_serializing_if = "Option::is_none")]
266 pub recorded_at: Option<DateTime<Utc>>,
267 #[serde(default, skip_serializing_if = "Option::is_none")]
268 pub hash: Option<String>,
269 pub bids: Vec<PriceLevel>,
270 pub asks: Vec<PriceLevel>,
271}
272
273pub fn sort_bids(levels: &mut [PriceLevel]) {
276 levels.sort_unstable_by_key(|l| std::cmp::Reverse(l.price));
277}
278
279pub fn sort_asks(levels: &mut [PriceLevel]) {
282 levels.sort_unstable_by_key(|l| l.price);
283}
284
285#[inline]
290pub fn insert_bid(levels: &mut Vec<PriceLevel>, level: PriceLevel) {
291 let idx = levels.partition_point(|l| l.price > level.price);
292 levels.insert(idx, level);
293}
294
295#[inline]
298pub fn insert_ask(levels: &mut Vec<PriceLevel>, level: PriceLevel) {
299 let idx = levels.partition_point(|l| l.price < level.price);
300 levels.insert(idx, level);
301}
302
303pub fn apply_bid_level(levels: &mut Vec<PriceLevel>, level: PriceLevel) {
309 match levels.binary_search_by(|l| level.price.cmp(&l.price)) {
310 Ok(idx) => {
311 if level.size > 0.0 {
312 levels[idx] = level;
313 } else {
314 levels.remove(idx);
315 }
316 }
317 Err(idx) => {
318 if level.size > 0.0 {
319 levels.insert(idx, level);
320 }
321 }
322 }
323}
324
325pub fn apply_ask_level(levels: &mut Vec<PriceLevel>, level: PriceLevel) {
327 match levels.binary_search_by(|l| l.price.cmp(&level.price)) {
328 Ok(idx) => {
329 if level.size > 0.0 {
330 levels[idx] = level;
331 } else {
332 levels.remove(idx);
333 }
334 }
335 Err(idx) => {
336 if level.size > 0.0 {
337 levels.insert(idx, level);
338 }
339 }
340 }
341}
342
343#[derive(Debug, Clone, Deserialize)]
344pub struct RestPriceLevel {
345 pub price: String,
346 pub size: String,
347}