use std::collections::BTreeMap;
use crate::live::market::book::halfway;
use crate::live::{BookUpdate, Data, Frame, Gap, Level};
use crate::utils::{Dec, MarketId};
pub const DEFAULT_DEPTH: usize = 20;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Book {
market: MarketId,
bids: BTreeMap<Dec, Dec>,
asks: BTreeMap<Dec, Dec>,
nonce: Option<u64>,
last_updated_at: u64,
timestamp: u64,
}
impl Book {
#[must_use]
pub const fn new(market: MarketId) -> Book {
Book { market, bids: BTreeMap::new(), asks: BTreeMap::new(), nonce: None, last_updated_at: 0, timestamp: 0 }
}
#[must_use]
pub const fn market(&self) -> MarketId {
self.market
}
#[must_use]
pub const fn is_ready(&self) -> bool {
self.nonce.is_some()
}
#[must_use]
pub const fn nonce(&self) -> Option<u64> {
self.nonce
}
#[must_use]
pub const fn last_updated_at(&self) -> u64 {
self.last_updated_at
}
#[must_use]
pub const fn timestamp(&self) -> u64 {
self.timestamp
}
pub fn snapshot(&mut self, update: &BookUpdate) {
self.bids.clear();
self.asks.clear();
put(&mut self.bids, &update.bids);
put(&mut self.asks, &update.asks);
self.nonce = Some(update.nonce);
self.last_updated_at = update.last_updated_at;
self.timestamp = update.timestamp;
}
pub fn update(&mut self, update: &BookUpdate) -> Result<bool, Gap> {
let Some(expected) = self.nonce.filter(|_| update.market == self.market) else { return Ok(false) };
if update.begin_nonce != expected {
self.reset();
return Err(Gap { market: self.market, expected, got: update.begin_nonce });
}
put(&mut self.bids, &update.bids);
put(&mut self.asks, &update.asks);
self.nonce = Some(update.nonce);
self.last_updated_at = update.last_updated_at;
self.timestamp = update.timestamp;
Ok(true)
}
pub fn apply(&mut self, frame: &Frame) -> Result<bool, Gap> {
match frame {
Frame::Snapshot(Data::OrderBook(update)) if update.market == self.market => {
self.snapshot(update);
Ok(true)
}
Frame::Update(Data::OrderBook(update)) => self.update(update),
Frame::Gap(gap) if gap.market == self.market => {
self.reset();
Ok(true)
}
_ => Ok(false),
}
}
pub fn reset(&mut self) {
self.bids.clear();
self.asks.clear();
self.nonce = None;
}
pub fn bids(&self) -> impl Iterator<Item = Level> + '_ {
self.bids.iter().rev().map(|(price, size)| Level { price: *price, size: *size })
}
pub fn asks(&self) -> impl Iterator<Item = Level> + '_ {
self.asks.iter().map(|(price, size)| Level { price: *price, size: *size })
}
#[must_use]
pub fn top(&self, depth: usize) -> (Vec<Level>, Vec<Level>) {
(self.bids().take(depth).collect(), self.asks().take(depth).collect())
}
#[must_use]
pub fn depth(&self) -> (usize, usize) {
(self.bids.len(), self.asks.len())
}
#[must_use]
pub fn best_bid(&self) -> Option<Level> {
self.bids().next()
}
#[must_use]
pub fn best_ask(&self) -> Option<Level> {
self.asks().next()
}
#[must_use]
pub fn mid(&self) -> Option<Dec> {
halfway(self.best_bid()?.price, self.best_ask()?.price)
}
#[must_use]
pub fn spread(&self) -> Option<Dec> {
self.best_ask()?.price.checked_sub(self.best_bid()?.price)
}
}
fn put(side: &mut BTreeMap<Dec, Dec>, levels: &[Level]) {
for level in levels {
match level.size.is_positive() {
true => side.insert(level.price, level.size),
false => side.remove(&level.price),
};
}
}
#[cfg(test)]
#[path = "tests/book.rs"]
mod tests;