use serde::Deserialize;
use crate::client::{ApiError, Transport, info, info_request};
use crate::utils::Json;
use crate::utils::dec::{Dec, Round};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Precision {
figures: Option<u32>,
step: Option<u32>,
}
impl Precision {
pub const FULL: Precision = Precision { figures: None, step: None };
#[must_use]
pub const fn figures(figures: u32) -> Option<Precision> {
match figures {
2..=5 => Some(Precision { figures: Some(figures), step: None }),
_ => None,
}
}
#[must_use]
pub const fn step(self, step: u32) -> Option<Precision> {
match (self.figures, step) {
(Some(5), 1 | 2 | 5) => Some(Precision { figures: Some(5), step: Some(step) }),
_ => None,
}
}
}
#[must_use]
pub fn l2_book_request(coin: &str, precision: Precision) -> String {
let number = |value: u32| Json::Number(value.to_string());
let figures = precision.figures.map(|figures| ("nSigFigs", number(figures)));
let step = precision.step.map(|step| ("mantissa", number(step)));
let fields: Vec<(&str, Json)> =
[Some(("coin", Json::String(coin.to_owned()))), figures, step].into_iter().flatten().collect();
info_request("l2Book", &fields)
}
pub async fn l2_book(transport: &impl Transport, coin: &str, precision: Precision) -> Result<Book, ApiError> {
info(transport, l2_book_request(coin, precision)).await
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
pub struct Level {
pub px: Dec,
pub sz: Dec,
pub n: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(from = "Raw")]
pub struct Book {
pub coin: String,
pub time: u64,
pub bids: Vec<Level>,
pub asks: Vec<Level>,
pub spread: Option<Dec>,
}
#[derive(Deserialize)]
struct Raw {
coin: String,
time: u64,
levels: (Vec<Level>, Vec<Level>),
#[serde(default)]
spread: Option<Dec>,
}
impl From<Raw> for Book {
fn from(raw: Raw) -> Self {
let (bids, asks) = raw.levels;
Book { coin: raw.coin, time: raw.time, bids, asks, spread: raw.spread }
}
}
impl Book {
#[must_use]
pub fn touch(&self) -> (Option<Dec>, Option<Dec>) {
(self.bids.first().map(|level| level.px), self.asks.first().map(|level| level.px))
}
#[must_use]
pub fn mid(&self) -> Option<Dec> {
let (Some(bid), Some(ask)) = self.touch() else { return None };
halfway(bid, ask)
}
}
fn halfway(bid: Dec, ask: Dec) -> Option<Dec> {
let places = bid.places().max(ask.places()).saturating_add(1);
bid.checked_add(ask)?.checked_div(Dec::from_int(2), places, Round::Down)
}
#[cfg(test)]
#[path = "tests/book.rs"]
mod tests;