sequence-algo-sdk 0.4.0

Sequence Markets Algo SDK — write HFT trading algos in Rust, compile to WASM, deploy to Sequence
Documentation
//! Runtime parameter configuration — live-tunable strategy parameters.
//!
//! Strategies declare typed parameters that can be adjusted at runtime
//! without redeploying WASM. The host writes parameters to a fixed WASM
//! memory region; the strategy reads them on each tick.
//!
//! # Example
//! ```rust,ignore
//! use algo_sdk::config::ConfigRegion;
//!
//! // In your strategy's on_tick:
//! let config = unsafe { ConfigRegion::read() };
//! let spread_target_bps = config.get_i32(0);  // param slot 0
//! let max_position = config.get_i64(1);        // param slot 1
//! ```

/// WASM memory offset for the config region.
/// Located after PoolStateTable (0x23000 + 3088 = ~0x23C10). Safe gap to 0x24000.
pub const CONFIG_REGION_WASM_OFFSET: u32 = 0x24000;

/// Maximum number of parameter slots.
pub const MAX_CONFIG_PARAMS: usize = 32;

/// Runtime configuration region — read by strategy, written by host.
///
/// Fixed-size (528 bytes). Each slot is 8 bytes (i64) with a type tag.
/// Slot 0..MAX_CONFIG_PARAMS-1 are available for user-defined parameters.
#[derive(Clone, Copy)]
#[repr(C)]
pub struct ConfigRegion {
    /// ABI version (currently 1).
    pub version: u8,
    /// Number of populated parameter slots.
    pub param_count: u8,
    /// Incremented on every config update (strategy can detect changes).
    pub update_seq: u16,
    pub _pad: [u8; 4],
    /// Timestamp of last config update (nanoseconds since epoch).
    pub update_ns: u64,
    /// Parameter values (i64, interpret per your schema).
    pub params: [i64; MAX_CONFIG_PARAMS],
    /// Parameter type tags: 0=i64, 1=f64_bits, 2=bool (0/1), 3=bps (i32 in lower 32 bits).
    pub types: [u8; MAX_CONFIG_PARAMS],
}

impl Default for ConfigRegion {
    fn default() -> Self {
        Self {
            version: 1,
            param_count: 0,
            update_seq: 0,
            _pad: [0; 4],
            update_ns: 0,
            params: [0; MAX_CONFIG_PARAMS],
            types: [0; MAX_CONFIG_PARAMS],
        }
    }
}

/// Parameter type tag.
pub mod ParamType {
    /// Raw i64.
    pub const INT: u8 = 0;
    /// f64 stored as bit pattern (use f64::from_bits).
    pub const FLOAT: u8 = 1;
    /// Boolean (0 or 1).
    pub const BOOL: u8 = 2;
    /// Basis points (i32 in lower 32 bits).
    pub const BPS: u8 = 3;
}

impl ConfigRegion {
    /// Read the config region from WASM memory.
    ///
    /// # Safety
    /// Only valid inside a WASM callback when the host has written config data.
    #[cfg(target_arch = "wasm32")]
    #[inline(always)]
    pub unsafe fn read() -> &'static Self {
        &*(CONFIG_REGION_WASM_OFFSET as *const Self)
    }

    /// Get raw i64 value at slot index.
    #[inline(always)]
    pub fn get_i64(&self, slot: usize) -> i64 {
        if slot < MAX_CONFIG_PARAMS { self.params[slot] } else { 0 }
    }

    /// Get i32 value at slot index (lower 32 bits).
    #[inline(always)]
    pub fn get_i32(&self, slot: usize) -> i32 {
        self.get_i64(slot) as i32
    }

    /// Get f64 value at slot index (from bit pattern).
    #[inline(always)]
    pub fn get_f64(&self, slot: usize) -> f64 {
        f64::from_bits(self.get_i64(slot) as u64)
    }

    /// Get bool value at slot index.
    #[inline(always)]
    pub fn get_bool(&self, slot: usize) -> bool {
        self.get_i64(slot) != 0
    }

    /// Get basis points value at slot index.
    #[inline(always)]
    pub fn get_bps(&self, slot: usize) -> i32 {
        self.get_i32(slot)
    }

    /// Whether config has been updated (compare against last seen seq).
    #[inline(always)]
    pub fn is_updated(&self, last_seq: u16) -> bool {
        self.update_seq != last_seq
    }

    /// Number of populated parameters.
    #[inline(always)]
    pub fn count(&self) -> usize {
        self.param_count as usize
    }
}

// Compile-time ABI check
const _: () = assert!(
    core::mem::size_of::<ConfigRegion>() == 304,
    "ConfigRegion must be exactly 304 bytes"
);

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn config_region_size() {
        // 1 + 1 + 2 + 4 + 8 + 32*8 + 32 = 304
        assert_eq!(core::mem::size_of::<ConfigRegion>(), 304);
    }

    #[test]
    fn config_region_get_params() {
        let mut config = ConfigRegion::default();
        config.param_count = 3;
        config.params[0] = 50; // spread target bps
        config.params[1] = 1_000_000_000; // max position 1e8
        config.params[2] = f64::to_bits(0.5) as i64; // some float param
        config.types[0] = ParamType::BPS;
        config.types[1] = ParamType::INT;
        config.types[2] = ParamType::FLOAT;

        assert_eq!(config.get_bps(0), 50);
        assert_eq!(config.get_i64(1), 1_000_000_000);
        assert!((config.get_f64(2) - 0.5).abs() < 0.001);
    }

    #[test]
    fn config_region_update_detection() {
        let mut config = ConfigRegion::default();
        assert!(!config.is_updated(0));
        config.update_seq = 1;
        assert!(config.is_updated(0));
        assert!(!config.is_updated(1));
    }

    #[test]
    fn config_region_out_of_bounds() {
        let config = ConfigRegion::default();
        assert_eq!(config.get_i64(99), 0);
        assert_eq!(config.get_i32(99), 0);
    }
}