sequence-algo-sdk 0.4.0

Sequence Markets Algo SDK — write HFT trading algos in Rust, compile to WASM, deploy to Sequence
Documentation
//! Order book types — L2 depth, price levels.

// =============================================================================
// PRICE LEVEL
// =============================================================================

/// Single price level in the order book.
#[derive(Debug, Clone, Copy, Default)]
#[repr(C)]
pub struct Level {
    pub px_1e9: u64, // Price × 10⁹
    pub sz_1e8: u64, // Size × 10⁸
}

impl Level {
    pub const EMPTY: Self = Self {
        px_1e9: 0,
        sz_1e8: 0,
    };

    #[inline(always)]
    pub fn is_valid(&self) -> bool {
        self.px_1e9 > 0
    }
}

// =============================================================================
// L2 ORDER BOOK - 20 levels each side
// =============================================================================

/// L2 order book with up to 20 levels per side.
/// Total size: 688 bytes (fits in L1 cache).
#[derive(Clone, Copy)]
#[repr(C)]
pub struct L2Book {
    pub bids: [Level; 20], // Best (index 0) to worst
    pub asks: [Level; 20], // Best (index 0) to worst
    pub bid_ct: u8,        // Valid bid levels
    pub ask_ct: u8,        // Valid ask levels
    pub symbol_id: u16,
    pub _pad: u32,
    pub recv_ns: u64, // Receive timestamp
}

impl Default for L2Book {
    fn default() -> Self {
        Self {
            bids: [Level::EMPTY; 20],
            asks: [Level::EMPTY; 20],
            bid_ct: 0,
            ask_ct: 0,
            symbol_id: 0,
            _pad: 0,
            recv_ns: 0,
        }
    }
}

impl L2Book {
    #[inline(always)]
    pub fn best_bid(&self) -> Option<&Level> {
        if self.bid_ct > 0 && self.bids[0].px_1e9 > 0 {
            Some(&self.bids[0])
        } else {
            None
        }
    }

    #[inline(always)]
    pub fn best_ask(&self) -> Option<&Level> {
        if self.ask_ct > 0 && self.asks[0].px_1e9 > 0 {
            Some(&self.asks[0])
        } else {
            None
        }
    }

    #[inline(always)]
    pub fn mid_px_1e9(&self) -> u64 {
        if self.bid_ct == 0 || self.ask_ct == 0 {
            return 0;
        }
        (self.bids[0].px_1e9 + self.asks[0].px_1e9) / 2
    }

    /// Raw unsigned spread in 1e9 price units.
    /// Returns 0 for crossed books (ask < bid) due to `saturating_sub`.
    /// Use `spread_signed_1e9()` or `is_crossed()` for proper handling.
    #[inline(always)]
    pub fn spread_1e9(&self) -> u64 {
        if self.bid_ct == 0 || self.ask_ct == 0 {
            return u64::MAX;
        }
        self.asks[0].px_1e9.saturating_sub(self.bids[0].px_1e9)
    }

    /// Signed spread in 1e9 price units. Negative = crossed book.
    #[inline(always)]
    pub fn spread_signed_1e9(&self) -> i64 {
        if self.bid_ct == 0 || self.ask_ct == 0 {
            return i64::MAX;
        }
        self.asks[0].px_1e9 as i64 - self.bids[0].px_1e9 as i64
    }

    /// Spread in integer basis points (truncated).
    /// WARNING: Returns 0 for sub-bps spreads common on liquid assets (BTC, ETH).
    /// Use `spread_bps_x1000()` for milli-bps precision on liquid markets.
    #[inline(always)]
    pub fn spread_bps(&self) -> u32 {
        let mid = self.mid_px_1e9();
        if mid == 0 {
            return u32::MAX;
        }
        ((self.spread_1e9() * 10_000) / mid) as u32
    }

    /// Spread in milli-basis-points (1 bps = 1000 milli-bps), signed.
    /// Handles sub-bps precision for liquid markets and negative values for crossed books.
    /// Uses u128 intermediate to avoid overflow on high-priced assets.
    #[inline(always)]
    pub fn spread_bps_x1000(&self) -> i32 {
        let mid = self.mid_px_1e9();
        if mid == 0 {
            return i32::MAX;
        }
        let spread = self.asks[0].px_1e9 as i128 - self.bids[0].px_1e9 as i128;
        ((spread * 10_000_000) / mid as i128) as i32
    }

    /// Whether the book is crossed (best bid > best ask).
    /// Common in consolidated NBBO when different venues have different prices.
    #[inline(always)]
    pub fn is_crossed(&self) -> bool {
        self.bid_ct > 0 && self.ask_ct > 0 && self.bids[0].px_1e9 > self.asks[0].px_1e9
    }

    #[inline(always)]
    pub fn bid_depth_1e8(&self, levels: usize) -> u64 {
        let n = levels.min(self.bid_ct as usize);
        let mut sum = 0u64;
        for i in 0..n {
            sum += self.bids[i].sz_1e8;
        }
        sum
    }

    #[inline(always)]
    pub fn ask_depth_1e8(&self, levels: usize) -> u64 {
        let n = levels.min(self.ask_ct as usize);
        let mut sum = 0u64;
        for i in 0..n {
            sum += self.asks[i].sz_1e8;
        }
        sum
    }

    #[inline(always)]
    pub fn imbalance_bps(&self, levels: usize) -> i32 {
        let bid_depth = self.bid_depth_1e8(levels);
        let ask_depth = self.ask_depth_1e8(levels);
        let total = bid_depth + ask_depth;
        if total == 0 {
            return 0;
        }
        (((bid_depth as i64 - ask_depth as i64) * 10_000) / total as i64) as i32
    }
}