use crate::L2Book;
pub const MAX_VENUES: usize = 20;
pub const VENUE_KRAKEN: u8 = 1;
pub const VENUE_COINBASE: u8 = 2;
pub const VENUE_BINANCE: u8 = 3;
pub const VENUE_BITGET: u8 = 4;
pub const VENUE_CRYPTOCOM: u8 = 5;
pub const VENUE_BITMART: u8 = 6;
pub const VENUE_DEX: u8 = 7;
pub const VENUE_OKX: u8 = 8;
pub const VENUE_BYBIT: u8 = 9;
pub const VENUE_UNKNOWN: u8 = 10;
pub const VENUE_DEX_ETH: u8 = 11;
pub const VENUE_DEX_ARB: u8 = 12;
pub const VENUE_DEX_BASE: u8 = 13;
pub const VENUE_DEX_OP: u8 = 14;
pub const VENUE_DEX_POLY: u8 = 15;
pub const VENUE_DEX_SOL: u8 = 16;
pub const VENUE_HYPERLIQUID: u8 = 17;
#[inline(always)]
pub fn is_dex(venue_id: u8) -> bool {
venue_id == VENUE_DEX
|| (venue_id >= VENUE_DEX_ETH && venue_id <= VENUE_DEX_POLY)
|| venue_id == VENUE_DEX_SOL
}
#[inline(always)]
pub fn is_cex(venue_id: u8) -> bool {
(venue_id >= VENUE_KRAKEN && venue_id <= VENUE_BYBIT && venue_id != VENUE_DEX)
|| venue_id == VENUE_HYPERLIQUID
}
pub fn venue_name(venue_id: u8) -> &'static str {
match venue_id {
0 => "default",
VENUE_KRAKEN => "kraken",
VENUE_COINBASE => "coinbase",
VENUE_BINANCE => "binance",
VENUE_BITGET => "bitget",
VENUE_CRYPTOCOM => "cryptocom",
VENUE_BITMART => "bitmart",
VENUE_DEX => "dex",
VENUE_OKX => "okx",
VENUE_BYBIT => "bybit",
VENUE_UNKNOWN => "unknown",
VENUE_DEX_ETH => "dex-eth",
VENUE_DEX_ARB => "dex-arb",
VENUE_DEX_BASE => "dex-base",
VENUE_DEX_OP => "dex-op",
VENUE_DEX_POLY => "dex-poly",
VENUE_DEX_SOL => "dex-sol",
VENUE_HYPERLIQUID => "hyperliquid",
_ => "unknown",
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct NbboSnapshot {
pub nbbo_bid_px_1e9: u64,
pub nbbo_ask_px_1e9: u64,
pub nbbo_bid_sz_1e8: u64,
pub nbbo_ask_sz_1e8: u64,
pub nbbo_bid_venue: u8, pub nbbo_ask_venue: u8, pub venue_ct: u8, pub symbol_id: u8,
pub sequence: u32, pub venue_ids: [u8; MAX_VENUES],
pub venue_bid_px: [u64; MAX_VENUES],
pub venue_ask_px: [u64; MAX_VENUES],
pub venue_bid_sz: [u32; MAX_VENUES],
pub venue_ask_sz: [u32; MAX_VENUES],
pub venue_update_ms: [u16; MAX_VENUES], pub recv_ns: u64,
pub venue_region_ids: [u8; MAX_VENUES],
}
impl Default for NbboSnapshot {
fn default() -> Self {
Self {
nbbo_bid_px_1e9: 0,
nbbo_ask_px_1e9: 0,
nbbo_bid_sz_1e8: 0,
nbbo_ask_sz_1e8: 0,
nbbo_bid_venue: 0,
nbbo_ask_venue: 0,
venue_ct: 0,
symbol_id: 0,
sequence: 0,
venue_ids: [0; MAX_VENUES],
venue_bid_px: [0; MAX_VENUES],
venue_ask_px: [0; MAX_VENUES],
venue_bid_sz: [0; MAX_VENUES],
venue_ask_sz: [0; MAX_VENUES],
venue_update_ms: [0; MAX_VENUES],
recv_ns: 0,
venue_region_ids: [0; MAX_VENUES],
}
}
}
impl NbboSnapshot {
#[inline(always)]
pub fn is_crossed(&self) -> bool {
self.nbbo_bid_px_1e9 > 0
&& self.nbbo_ask_px_1e9 > 0
&& self.nbbo_bid_px_1e9 > self.nbbo_ask_px_1e9
&& self.nbbo_bid_venue != self.nbbo_ask_venue
}
#[inline(always)]
pub fn venue_bid(&self, slot: usize) -> u64 {
if slot < self.venue_ct as usize {
self.venue_bid_px[slot]
} else {
0
}
}
#[inline(always)]
pub fn venue_ask(&self, slot: usize) -> u64 {
if slot < self.venue_ct as usize {
self.venue_ask_px[slot]
} else {
0
}
}
#[inline(always)]
pub fn is_venue_stale(&self, slot: usize, max_ms: u16) -> bool {
if slot < self.venue_ct as usize {
self.venue_update_ms[slot] > max_ms
} else {
true
}
}
#[inline]
pub fn slot_for_venue(&self, venue_id: u8) -> Option<usize> {
let ct = self.venue_ct as usize;
for i in 0..ct {
if self.venue_ids[i] == venue_id {
return Some(i);
}
}
None
}
#[inline(always)]
pub fn best_bid_venue_id(&self) -> u8 {
self.venue_ids[self.nbbo_bid_venue as usize]
}
#[inline(always)]
pub fn best_ask_venue_id(&self) -> u8 {
self.venue_ids[self.nbbo_ask_venue as usize]
}
}
pub const VENUE_BOOKS_WASM_OFFSET: u32 = 0x10000;
#[derive(Clone)]
#[repr(C)]
pub struct VenueBooks {
pub merged: L2Book,
pub book_ct: u8,
pub symbol_id: u16,
pub _pad: [u8; 5],
pub venue_ids: [u8; MAX_VENUES],
pub venue_region_ids: [u8; MAX_VENUES],
pub books: [L2Book; MAX_VENUES],
}
impl Default for VenueBooks {
fn default() -> Self {
Self {
merged: L2Book::default(),
book_ct: 0,
symbol_id: 0,
_pad: [0; 5],
venue_ids: [0; MAX_VENUES],
venue_region_ids: [0; MAX_VENUES],
books: [L2Book::default(); MAX_VENUES],
}
}
}
impl VenueBooks {
#[inline]
pub fn book_for_venue(&self, venue_id: u8) -> Option<&L2Book> {
let ct = self.book_ct as usize;
for i in 0..ct {
if self.venue_ids[i] == venue_id {
return Some(&self.books[i]);
}
}
None
}
#[inline(always)]
pub fn book_at_slot(&self, slot: usize) -> &L2Book {
if slot < self.book_ct as usize {
&self.books[slot]
} else {
&self.books[0]
}
}
#[inline]
pub fn book_for_venue_region(&self, venue_id: u8, region_id: u8) -> Option<&L2Book> {
let ct = self.book_ct as usize;
for i in 0..ct {
if self.venue_ids[i] == venue_id && self.venue_region_ids[i] == region_id {
return Some(&self.books[i]);
}
}
None
}
#[inline]
pub fn has_depth_for(&self, venue_id: u8) -> bool {
self.book_for_venue(venue_id)
.map_or(false, |b| b.bid_ct > 0 || b.ask_ct > 0)
}
#[inline(always)]
pub fn venue_id_at(&self, slot: usize) -> u8 {
if slot < self.book_ct as usize {
self.venue_ids[slot]
} else {
0
}
}
#[inline]
pub fn cex_count(&self) -> usize {
let ct = self.book_ct as usize;
(0..ct).filter(|&i| is_cex(self.venue_ids[i])).count()
}
#[inline]
pub fn dex_count(&self) -> usize {
let ct = self.book_ct as usize;
(0..ct).filter(|&i| is_dex(self.venue_ids[i])).count()
}
}