df_hyperliquid_core/markets/
book.rs1use serde::Deserialize;
5
6use crate::client::{ApiError, Transport, info, info_request};
7use crate::utils::Json;
8use crate::utils::dec::{Dec, Round};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13pub struct Precision {
14 figures: Option<u32>,
15 step: Option<u32>,
16}
17
18impl Precision {
19 pub const FULL: Precision = Precision { figures: None, step: None };
20
21 #[must_use]
23 pub const fn figures(figures: u32) -> Option<Precision> {
24 match figures {
25 2..=5 => Some(Precision { figures: Some(figures), step: None }),
26 _ => None,
27 }
28 }
29
30 #[must_use]
32 pub const fn step(self, step: u32) -> Option<Precision> {
33 match (self.figures, step) {
34 (Some(5), 1 | 2 | 5) => Some(Precision { figures: Some(5), step: Some(step) }),
35 _ => None,
36 }
37 }
38}
39
40#[must_use]
43pub fn l2_book_request(coin: &str, precision: Precision) -> String {
44 let number = |value: u32| Json::Number(value.to_string());
45 let figures = precision.figures.map(|figures| ("nSigFigs", number(figures)));
46 let step = precision.step.map(|step| ("mantissa", number(step)));
47 let fields: Vec<(&str, Json)> =
48 [Some(("coin", Json::String(coin.to_owned()))), figures, step].into_iter().flatten().collect();
49 info_request("l2Book", &fields)
50}
51
52pub async fn l2_book(transport: &impl Transport, coin: &str, precision: Precision) -> Result<Book, ApiError> {
53 info(transport, l2_book_request(coin, precision)).await
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
58pub struct Level {
59 pub px: Dec,
60 pub sz: Dec,
61 pub n: u32,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
68#[serde(from = "Raw")]
69pub struct Book {
70 pub coin: String,
71 pub time: u64,
73 pub bids: Vec<Level>,
75 pub asks: Vec<Level>,
77 pub spread: Option<Dec>,
79}
80
81#[derive(Deserialize)]
83struct Raw {
84 coin: String,
85 time: u64,
86 levels: (Vec<Level>, Vec<Level>),
87 #[serde(default)]
88 spread: Option<Dec>,
89}
90
91impl From<Raw> for Book {
92 fn from(raw: Raw) -> Self {
93 let (bids, asks) = raw.levels;
94 Book { coin: raw.coin, time: raw.time, bids, asks, spread: raw.spread }
95 }
96}
97
98impl Book {
99 #[must_use]
101 pub fn touch(&self) -> (Option<Dec>, Option<Dec>) {
102 (self.bids.first().map(|level| level.px), self.asks.first().map(|level| level.px))
103 }
104
105 #[must_use]
108 pub fn mid(&self) -> Option<Dec> {
109 let (Some(bid), Some(ask)) = self.touch() else { return None };
110 halfway(bid, ask)
111 }
112}
113
114fn halfway(bid: Dec, ask: Dec) -> Option<Dec> {
116 let places = bid.places().max(ask.places()).saturating_add(1);
117 bid.checked_add(ask)?.checked_div(Dec::from_int(2), places, Round::Down)
118}
119
120#[cfg(test)]
121#[path = "tests/book.rs"]
122mod tests;