use pretty_simple_display::{DebugPretty, DisplaySimple};
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[skip_serializing_none]
#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
pub struct BookSummary {
pub instrument_name: String,
pub base_currency: String,
pub quote_currency: String,
pub volume: f64,
pub volume_usd: f64,
pub open_interest: f64,
pub price_change: Option<f64>,
pub mark_price: f64,
pub mark_iv: Option<f64>,
pub bid_price: Option<f64>,
pub ask_price: Option<f64>,
pub mid_price: Option<f64>,
pub last: Option<f64>,
pub high: Option<f64>,
pub low: Option<f64>,
pub estimated_delivery_price: Option<f64>,
pub current_funding: Option<f64>,
pub funding_8h: Option<f64>,
pub creation_timestamp: i64,
pub underlying_index: Option<String>,
pub underlying_price: Option<f64>,
pub interest_rate: Option<f64>,
}
impl BookSummary {
pub fn new(
instrument_name: String,
base_currency: String,
quote_currency: String,
mark_price: f64,
creation_timestamp: i64,
) -> Self {
Self {
instrument_name,
base_currency,
quote_currency,
volume: 0.0,
volume_usd: 0.0,
open_interest: 0.0,
price_change: None,
mark_price,
mark_iv: None,
bid_price: None,
ask_price: None,
mid_price: None,
last: None,
high: None,
low: None,
estimated_delivery_price: None,
current_funding: None,
funding_8h: None,
creation_timestamp,
underlying_index: None,
underlying_price: None,
interest_rate: None,
}
}
pub fn with_volume(mut self, volume: f64, volume_usd: f64) -> Self {
self.volume = volume;
self.volume_usd = volume_usd;
self
}
pub fn with_prices(
mut self,
bid: Option<f64>,
ask: Option<f64>,
last: Option<f64>,
high: Option<f64>,
low: Option<f64>,
) -> Self {
self.bid_price = bid;
self.ask_price = ask;
self.last = last;
self.high = high;
self.low = low;
if let (Some(bid), Some(ask)) = (bid, ask) {
self.mid_price = Some((bid + ask) / 2.0);
}
self
}
pub fn with_open_interest(mut self, open_interest: f64) -> Self {
self.open_interest = open_interest;
self
}
pub fn with_price_change(mut self, price_change: f64) -> Self {
self.price_change = Some(price_change);
self
}
pub fn with_iv(mut self, mark_iv: f64) -> Self {
self.mark_iv = Some(mark_iv);
self
}
pub fn with_funding(mut self, current: f64, funding_8h: f64) -> Self {
self.current_funding = Some(current);
self.funding_8h = Some(funding_8h);
self
}
pub fn with_delivery_price(mut self, price: f64) -> Self {
self.estimated_delivery_price = Some(price);
self
}
pub fn spread(&self) -> Option<f64> {
match (self.bid_price, self.ask_price) {
(Some(bid), Some(ask)) => Some(ask - bid),
_ => None,
}
}
pub fn spread_percentage(&self) -> Option<f64> {
match (self.spread(), self.mid_price) {
(Some(spread), Some(mid)) if mid > 0.0 => Some((spread / mid) * 100.0),
_ => None,
}
}
pub fn is_perpetual(&self) -> bool {
self.instrument_name.contains("PERPETUAL")
}
pub fn is_option(&self) -> bool {
!self.is_perpetual()
&& (self.instrument_name.ends_with("-C") || self.instrument_name.ends_with("-P"))
}
pub fn is_future(&self) -> bool {
!self.is_perpetual() && !self.is_option()
}
pub fn price_change_absolute(&self) -> Option<f64> {
self.price_change.map(|change| {
if let Some(last) = self.last {
last * (change / 100.0)
} else {
self.mark_price * (change / 100.0)
}
})
}
}
#[derive(DebugPretty, DisplaySimple, Clone, PartialEq, Serialize, Deserialize)]
pub struct BookSummaries {
pub summaries: Vec<BookSummary>,
}
impl BookSummaries {
pub fn new() -> Self {
Self {
summaries: Vec::new(),
}
}
pub fn add(&mut self, summary: BookSummary) {
self.summaries.push(summary);
}
pub fn by_currency(&self, currency: String) -> Vec<&BookSummary> {
self.summaries
.iter()
.filter(|s| s.base_currency == currency)
.collect()
}
pub fn perpetuals(&self) -> Vec<&BookSummary> {
self.summaries.iter().filter(|s| s.is_perpetual()).collect()
}
pub fn options(&self) -> Vec<&BookSummary> {
self.summaries.iter().filter(|s| s.is_option()).collect()
}
pub fn futures(&self) -> Vec<&BookSummary> {
self.summaries.iter().filter(|s| s.is_future()).collect()
}
pub fn sort_by_volume(&mut self) {
self.summaries
.sort_by(|a, b| b.volume_usd.partial_cmp(&a.volume_usd).unwrap());
}
pub fn sort_by_open_interest(&mut self) {
self.summaries
.sort_by(|a, b| b.open_interest.partial_cmp(&a.open_interest).unwrap());
}
}
impl Default for BookSummaries {
fn default() -> Self {
Self::new()
}
}
#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
pub struct OrderBookEntry {
pub price: f64,
pub amount: f64,
}
impl OrderBookEntry {
pub fn new(price: f64, amount: f64) -> Self {
Self { price, amount }
}
pub fn notional(&self) -> f64 {
self.price * self.amount
}
}
#[skip_serializing_none]
#[derive(DebugPretty, DisplaySimple, Clone, Serialize, Deserialize)]
pub struct OrderBook {
pub instrument_name: String,
pub timestamp: i64,
pub bids: Vec<OrderBookEntry>,
pub asks: Vec<OrderBookEntry>,
pub change_id: u64,
pub prev_change_id: Option<u64>,
}
impl OrderBook {
pub fn new(instrument_name: String, timestamp: i64, change_id: u64) -> Self {
Self {
instrument_name,
timestamp,
bids: Vec::new(),
asks: Vec::new(),
change_id,
prev_change_id: None,
}
}
pub fn best_bid(&self) -> Option<f64> {
self.bids.first().map(|entry| entry.price)
}
pub fn best_ask(&self) -> Option<f64> {
self.asks.first().map(|entry| entry.price)
}
pub fn spread(&self) -> Option<f64> {
match (self.best_ask(), self.best_bid()) {
(Some(ask), Some(bid)) => Some(ask - bid),
_ => None,
}
}
pub fn mid_price(&self) -> Option<f64> {
match (self.best_ask(), self.best_bid()) {
(Some(ask), Some(bid)) => Some((ask + bid) / 2.0),
_ => None,
}
}
pub fn total_bid_volume(&self) -> f64 {
self.bids.iter().map(|entry| entry.amount).sum()
}
pub fn total_ask_volume(&self) -> f64 {
self.asks.iter().map(|entry| entry.amount).sum()
}
pub fn volume_at_price(&self, price: f64, is_bid: bool) -> f64 {
let levels = if is_bid { &self.bids } else { &self.asks };
levels
.iter()
.find(|entry| (entry.price - price).abs() < f64::EPSILON)
.map(|entry| entry.amount)
.unwrap_or(0.0)
}
}