use const_decimal::Decimal;
use super::MarketUpdate;
use crate::{
market_update::market_update_trait::Exhausted,
order_filters::{
enforce_bid_ask_spread,
enforce_max_price,
enforce_min_price,
enforce_step_size,
},
prelude::{
Currency,
LimitOrder,
MarketState,
Mon,
Pending,
PriceFilter,
QuoteCurrency,
},
types::{
PriceFilterError,
TimestampNs,
UserOrderId,
},
};
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct Bba<I, const D: u8>
where
I: Mon<D>,
{
pub bid: QuoteCurrency<I, D>,
pub ask: QuoteCurrency<I, D>,
pub timestamp_exchange_ns: TimestampNs,
}
impl<I: Mon<D>, const D: u8> Bba<I, D> {
#[inline(always)]
pub fn mid_price(&self) -> QuoteCurrency<I, D> {
(self.bid + self.ask) / Decimal::TWO
}
}
impl<I, const D: u8> std::fmt::Display for Bba<I, D>
where
I: Mon<D>,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"bid: {}, ask: {}, ts: {}",
self.bid, self.ask, self.timestamp_exchange_ns
)
}
}
impl<I, const D: u8, BaseOrQuote> MarketUpdate<I, D, BaseOrQuote> for Bba<I, D>
where
I: Mon<D>,
BaseOrQuote: Currency<I, D>,
{
const CAN_FILL_LIMIT_ORDERS: bool = false;
fn limit_order_filled<UserOrderIdT: UserOrderId>(
&mut self,
_limit_order: &LimitOrder<I, D, BaseOrQuote, UserOrderIdT, Pending<I, D, BaseOrQuote>>,
) -> Option<(BaseOrQuote, Exhausted)> {
std::hint::cold_path();
unreachable!(
"This should never be called, because a best bid and ask update can never fill a limit order."
);
}
fn validate_market_update(
&self,
price_filter: &PriceFilter<I, D>,
) -> Result<(), PriceFilterError> {
enforce_min_price(price_filter.min_price(), self.bid)?;
enforce_min_price(price_filter.min_price(), self.ask)?;
enforce_max_price(price_filter.max_price(), self.bid)?;
enforce_max_price(price_filter.max_price(), self.ask)?;
enforce_step_size(price_filter.tick_size(), self.bid)?;
enforce_step_size(price_filter.tick_size(), self.ask)?;
enforce_bid_ask_spread(self.bid, self.ask)?;
Ok(())
}
#[inline(always)]
fn update_market_state(&self, market_state: &mut MarketState<I, D>) {
market_state.set_bid(self.bid);
market_state.set_ask(self.ask);
}
#[inline(always)]
fn timestamp_exchange_ns(&self) -> TimestampNs {
self.timestamp_exchange_ns
}
#[inline(always)]
fn can_fill_bids(&self) -> bool {
false
}
#[inline(always)]
fn can_fill_asks(&self) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::BaseCurrency;
#[test]
fn size_of_bba() {
assert_eq!(size_of::<Bba<i32, 4>>(), 16);
assert_eq!(size_of::<Bba<i64, 4>>(), 24);
}
#[test]
fn bba_update() {
let bba = Bba {
bid: QuoteCurrency::<i64, 1>::new(100, 0),
ask: QuoteCurrency::new(101, 0),
timestamp_exchange_ns: 1.into(),
};
assert_eq!(bba.mid_price(), QuoteCurrency::new(1005, 1));
assert!(!<Bba<i64, 1> as MarketUpdate<
i64,
1,
BaseCurrency<i64, 1>,
>>::can_fill_bids(&bba));
assert!(!<Bba<i64, 1> as MarketUpdate<
i64,
1,
BaseCurrency<i64, 1>,
>>::can_fill_asks(&bba));
}
#[test]
fn bba_update_display() {
let update = Bba {
bid: QuoteCurrency::<i64, 1>::new(100, 0),
ask: QuoteCurrency::new(101, 0),
timestamp_exchange_ns: 1.into(),
};
assert_eq!(
&update.to_string(),
"bid: 100.0 Quote, ask: 101.0 Quote, ts: 1"
);
}
}