sequence-algo-sdk 0.4.0

Sequence Markets Algo SDK — write HFT trading algos in Rust, compile to WASM, deploy to Sequence
Documentation
//! Execution events — fills, rejects, extended metadata.

// =============================================================================
// FILL EVENT
// =============================================================================

#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct Fill {
    pub order_id: u64,
    pub px_1e9: u64,
    pub qty_1e8: i64,
    pub recv_ns: u64, // Timestamp when fill was received
    pub side: i8,
    pub _pad: [u8; 7],
}

impl Fill {
    /// Elapsed time since a caller-provided start timestamp.
    /// Typical use: `fill.since_ms(start_ns)` where `start_ns` came from `book.recv_ns`.
    #[inline(always)]
    pub fn since_ns(&self, start_ns: u64) -> u64 {
        self.recv_ns.saturating_sub(start_ns)
    }

    #[inline(always)]
    pub fn since_us(&self, start_ns: u64) -> u64 {
        self.since_ns(start_ns) / 1_000
    }

    #[inline(always)]
    pub fn since_ms(&self, start_ns: u64) -> u64 {
        self.since_ns(start_ns) / 1_000_000
    }

    /// Symbol identifier (v0.4+). 0 = single-symbol or unset.
    /// In multi-symbol backtests, matches L2Book.symbol_id.
    /// Stored in _pad[0..2] as little-endian u16 for ABI backward compat.
    #[inline(always)]
    pub fn symbol_id(&self) -> u16 {
        u16::from_le_bytes([self._pad[0], self._pad[1]])
    }

    /// Set symbol identifier. Used by sim-engine orchestrator.
    #[inline(always)]
    pub fn set_symbol_id(&mut self, id: u16) {
        let bytes = id.to_le_bytes();
        self._pad[0] = bytes[0];
        self._pad[1] = bytes[1];
    }
}

// ABI size assertions — these must match the WASM memory layout expectations
const _: () = assert!(
    core::mem::size_of::<Fill>() == 40,
    "Fill size changed — ABI break"
);
const _: () = assert!(
    core::mem::size_of::<Reject>() == 16,
    "Reject size changed — ABI break"
);

// =============================================================================
// FILL EXTENDED METADATA
// =============================================================================

/// Extended fill metadata (NOT in WASM memory — delivered via separate channel).
///
/// Provides additional context about a fill that doesn't fit in the fixed-size
/// `Fill` struct in WASM shared memory. Delivered out-of-band by the runtime.
#[derive(Debug, Clone, Copy, Default)]
pub struct FillExt {
    /// 1 = maker, 0 = taker, 255 = unknown.
    pub is_maker: u8,
    /// Fee in 1e9 units. Negative = rebate.
    pub fee_1e9: i64,
    /// Estimated queue depth ahead at time of fill (1e8 units), or -1 if unknown.
    pub queue_ahead_1e8: i64,
}

// =============================================================================
// REJECT EVENT
// =============================================================================

/// Reject codes from exchange (matches cc_proto::RejectClass).
pub mod RejectCode {
    /// Unknown error
    pub const UNKNOWN: u8 = 0;
    /// Insufficient balance/funds
    pub const INSUFFICIENT_BALANCE: u8 = 1;
    /// Invalid parameters (price, qty, symbol)
    pub const INVALID_PARAMS: u8 = 2;
    /// Exchange rate limit hit
    pub const RATE_LIMIT: u8 = 3;
    /// Exchange temporarily unavailable
    pub const EXCHANGE_BUSY: u8 = 4;
    /// Network error
    pub const NETWORK: u8 = 5;
    /// Authentication error (invalid API key/secret)
    pub const AUTH: u8 = 6;

    // Internal reject codes (from risk engine, >=100)
    /// Risk check failed
    pub const RISK: u8 = 100;
    /// Position limit exceeded
    pub const POSITION_LIMIT: u8 = 101;
    /// Kill switch triggered
    pub const KILL_SWITCH: u8 = 102;
    /// Fat-finger: price deviates too far from reference (best bid/ask)
    pub const PRICE_DEVIATION: u8 = 103;
    /// Daily P&L loss limit breached — algo paused
    pub const DAILY_LOSS_LIMIT: u8 = 104;
    /// Venue ID not in registered venue set (multi-venue)
    pub const BAD_VENUE: u8 = 105;
    /// Target venue data is stale (multi-venue)
    pub const STALE_VENUE: u8 = 106;

    /// Get human-readable description for a reject code.
    pub fn to_str(code: u8) -> &'static str {
        match code {
            UNKNOWN => "UNKNOWN",
            INSUFFICIENT_BALANCE => "INSUFFICIENT_BALANCE",
            INVALID_PARAMS => "INVALID_PARAMS",
            RATE_LIMIT => "RATE_LIMIT",
            EXCHANGE_BUSY => "EXCHANGE_BUSY",
            NETWORK => "NETWORK",
            AUTH => "AUTH",
            RISK => "RISK_CHECK_FAILED",
            POSITION_LIMIT => "POSITION_LIMIT",
            KILL_SWITCH => "KILL_SWITCH",
            PRICE_DEVIATION => "PRICE_DEVIATION",
            DAILY_LOSS_LIMIT => "DAILY_LOSS_LIMIT",
            BAD_VENUE => "BAD_VENUE",
            STALE_VENUE => "STALE_VENUE",
            _ => "UNKNOWN",
        }
    }
}

#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct Reject {
    pub order_id: u64,
    pub code: u8,
    pub _pad: [u8; 7],
}

impl Reject {
    /// Get human-readable description of the reject reason.
    #[inline]
    pub fn reason(&self) -> &'static str {
        RejectCode::to_str(self.code)
    }

    /// Symbol identifier (v0.4+). 0 = single-symbol or unset.
    #[inline(always)]
    pub fn symbol_id(&self) -> u16 {
        u16::from_le_bytes([self._pad[0], self._pad[1]])
    }

    #[inline(always)]
    pub fn set_symbol_id(&mut self, id: u16) {
        let bytes = id.to_le_bytes();
        self._pad[0] = bytes[0];
        self._pad[1] = bytes[1];
    }
}