# sequence-algo-sdk
[](https://crates.io/crates/sequence-algo-sdk)
[](https://docs.rs/sequence-algo-sdk)
[](LICENSE-MIT)
Write ultra-low-latency trading algos in Rust, compile to WASM, deploy to [Sequence Markets](https://sequencemkts.com). Zero dependencies. The SDK provides two algo traits — **single-venue** and **multi-venue** — along with order book types, action buffers, and everything you need to build trading algorithms that run on Sequence's edge infrastructure.
## Quick Start
```toml
# Cargo.toml
[package]
name = "my-algo"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
sequence-algo-sdk = { version = "0.3", default-features = false }
[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
```
### Single-Venue Algo
Receives one venue's order book. Good for market making, momentum, mean reversion on a single exchange.
```rust
// src/lib.rs
#![no_std]
extern crate alloc;
use algo_sdk::*;
struct MyAlgo {
next_id: u64,
}
impl Algo for MyAlgo {
fn on_book(&mut self, book: &L2Book, state: &AlgoState, actions: &mut Actions) {
// Your trading logic here
if book.spread_bps() > 10 && state.is_flat() {
self.next_id += 1;
actions.buy(self.next_id, 1_000_000, book.bids[0].px_1e9 + 100);
}
}
fn on_fill(&mut self, _fill: &Fill, _state: &AlgoState) {}
fn on_reject(&mut self, _reject: &Reject) {}
fn on_shutdown(&mut self, _state: &AlgoState, actions: &mut Actions) {
actions.clear(); // Cancel all pending orders
}
}
export_algo!(MyAlgo { next_id: 0 });
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! { loop {} }
```
### Multi-Venue Algo
Receives an `NbboSnapshot` with the best bid/ask across **all** connected venues (up to 16 CEX + DEX), plus `VenueBooks` with full 20-level depth per venue. Use it for cross-venue arbitrage, multi-venue market making, or best-execution strategies.
```rust
// src/lib.rs
#![no_std]
extern crate alloc;
use algo_sdk::*;
struct ArbAlgo {
next_id: u64,
}
impl MultiVenueAlgo for ArbAlgo {
fn on_nbbo(
&mut self,
nbbo: &NbboSnapshot,
books: &VenueBooks,
state: &AlgoState,
actions: &mut Actions,
) {
// Need at least 2 venues and a crossed market
if nbbo.venue_ct < 2 || !nbbo.is_crossed() {
return;
}
// Skip stale venues (>3 seconds old)
let bid_v = nbbo.nbbo_bid_venue as usize;
let ask_v = nbbo.nbbo_ask_venue as usize;
if nbbo.is_venue_stale(bid_v, 3000) || nbbo.is_venue_stale(ask_v, 3000) {
return;
}
// Access per-venue depth for smarter sizing
if let Some(kraken_book) = books.book_for_venue(VENUE_KRAKEN) {
// Use full depth to size orders appropriately
let _depth_at_best = kraken_book.bids[0].sz_1e8;
}
// Route orders to specific venues
let bid_venue_id = nbbo.venue_ids[bid_v];
let ask_venue_id = nbbo.venue_ids[ask_v];
if state.is_flat() {
self.next_id += 1;
actions.ioc_buy_on(ask_venue_id, self.next_id, 1_000_000, nbbo.nbbo_ask_px_1e9);
self.next_id += 1;
actions.ioc_sell_on(bid_venue_id, self.next_id, 1_000_000, nbbo.nbbo_bid_px_1e9);
}
}
fn on_fill(&mut self, _fill: &Fill, _state: &AlgoState) {}
fn on_reject(&mut self, _reject: &Reject) {}
fn on_shutdown(&mut self, _state: &AlgoState, actions: &mut Actions) {
actions.clear();
}
}
export_multi_venue_algo!(ArbAlgo { next_id: 0 });
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! { loop {} }
```
## Install the CLI
### macOS / Linux
```bash
This detects your platform, downloads the latest `sequence` binary, verifies the SHA-256 checksum, and installs to `~/.local/bin`.
### Windows (PowerShell)
```powershell
Installs to `~/.sequence/bin`. You may need to add this directory to your PATH.
### Windows (Git Bash / WSL)
The shell installer also works in Git Bash, MSYS2, and WSL:
```bash
### Manual Download
Download the archive for your platform from [GitHub Releases](https://github.com/Bai-Funds/algo-sdk/releases) (look for tags starting with `cli/v`):
| macOS (Apple Silicon) | `sequence-aarch64-apple-darwin.tar.gz` |
| macOS (Intel) | `sequence-x86_64-apple-darwin.tar.gz` |
| Linux (x86_64) | `sequence-x86_64-unknown-linux-gnu.tar.gz` |
| Linux (ARM64) | `sequence-aarch64-unknown-linux-gnu.tar.gz` |
| Windows (x86_64) | `sequence-x86_64-pc-windows-msvc.zip` |
Extract and place `sequence` (or `sequence.exe` on Windows) somewhere on your PATH.
### Verify Installation
```bash
sequence --version
```
### Update to Latest Version
```bash
sequence update
```
This checks GitHub Releases for a newer `cli/v*` tag, downloads it, verifies the checksum, and replaces the current binary in-place. Works on macOS, Linux, and Windows.
## Build & Deploy
```bash
# Single-venue algo
sequence init my-algo
cd my-algo
sequence build
sequence deploy BTC-USD --start
sequence logs BTC-USD --follow
# Multi-venue algo
sequence init my-arb --multi-venue
cd my-arb
sequence build
sequence deploy ETH-USDC --start
```
Or build manually:
```bash
cargo build --target wasm32-unknown-unknown --release
```
## Paper Trading (Sandbox)
Test your algo against live market data without placing real orders:
```bash
# Log in with a sandbox key (seq_test_...)
sequence login --sandbox
# Deploy and run in sandbox mode
sequence --sandbox deploy BTC-USD --start
sequence --sandbox logs BTC-USD --follow
sequence --sandbox list
sequence --sandbox stop BTC-USD
```
Sandbox and live deployments are fully isolated — same symbol, same client, different infrastructure. You can run both simultaneously. For CI/CD, set `SEQUENCE_SANDBOX_API_KEY` instead of using `--sandbox`.
## Key Types
### Core
| `Algo` | Single-venue trait — `on_book`, `on_fill`, `on_reject`, `on_shutdown` |
| `MultiVenueAlgo` | Multi-venue trait — `on_nbbo`, `on_fill`, `on_reject`, `on_shutdown` |
| `L2Book` | 20-level order book (656 bytes, fits in L1 cache) |
| `AlgoState` | Position, open orders, session PnL, symbol metadata, and risk limits |
| `Actions` | Buffer for placing/canceling orders (up to 16 per tick) |
| `Fill` | Fill event with price, quantity, and timing |
| `Reject` | Rejection with typed error codes |
### Multi-Venue
| `NbboSnapshot` | Cross-venue NBBO with per-venue BBO, staleness tracking, up to 16 venues |
| `VenueBooks` | Merged + per-venue 20-level depth books (~11KB) |
| `PoolBooks` | Per-pool depth from DEX liquidity pools (~23KB, up to 32 pools) |
| `PoolMeta` | Pool identity: address, chain, protocol, fee tier (40 bytes) |
| `MAX_VENUES` | Maximum venues in an `NbboSnapshot` (16) |
| `MAX_POOLS` | Maximum individual pool slots in a `PoolBooks` (32) |
## Venue Reference
Every venue has a constant ID you can use for filtering, order routing, and depth lookups.
### CEX Venues
| `VENUE_KRAKEN` | 1 | `"kraken"` | Kraken exchange |
| `VENUE_COINBASE` | 2 | `"coinbase"` | Coinbase exchange |
| `VENUE_BINANCE` | 3 | `"binance"` | Binance exchange |
| `VENUE_BITGET` | 4 | `"bitget"` | Bitget exchange |
| `VENUE_CRYPTOCOM` | 5 | `"cryptocom"` | Crypto.com exchange |
| `VENUE_BITMART` | 6 | `"bitmart"` | BitMart exchange |
| `VENUE_OKX` | 8 | `"okx"` | OKX exchange |
| `VENUE_BYBIT` | 9 | `"bybit"` | Bybit exchange |
| `VENUE_UNKNOWN` | 10 | `"unknown"` | Unknown/unrecognized venue |
### DEX Venues
| `VENUE_DEX` | 7 | `"dex"` | Aggregated | All chains combined |
| `VENUE_DEX_ETH` | 11 | `"dex-eth"` | Ethereum | Uniswap V2/V3, Curve, Balancer V2 |
| `VENUE_DEX_ARB` | 12 | `"dex-arb"` | Arbitrum | Uniswap V3, Camelot (Algebra V3) |
| `VENUE_DEX_BASE` | 13 | `"dex-base"` | Base | Uniswap V3, Aerodrome (Solidly) |
| `VENUE_DEX_OP` | 14 | `"dex-op"` | Optimism | Uniswap V3, Velodrome (Solidly) |
| `VENUE_DEX_POLY` | 15 | `"dex-poly"` | Polygon | Uniswap V2, Balancer V2, Curve |
| `VENUE_DEX_SOL` | 16 | `"dex-sol"` | Solana | Raydium CLMM, Orca Whirlpool (via Jupiter) |
**Note:** Value 10 is reserved. Per-chain DEX venues (11-16) provide chain-specific depth, while `VENUE_DEX` (7) aggregates across all chains.
### Venue Helper Functions
```rust
// Classification
is_dex(VENUE_DEX_ARB) // true — any DEX venue (7, 11-16)
is_dex(VENUE_DEX_SOL) // true — Solana is a DEX venue
is_cex(VENUE_KRAKEN) // true — any CEX venue (1-6, 8-9)
is_dex(VENUE_COINBASE) // false
// Name lookup
venue_name(VENUE_KRAKEN) // "kraken"
venue_name(VENUE_DEX_ARB) // "dex-arb"
venue_name(VENUE_DEX_SOL) // "dex-sol"
venue_name(0) // "unknown"
```
### Filtering by Venue Type
```rust
fn on_nbbo(&mut self, nbbo: &NbboSnapshot, books: &VenueBooks, state: &AlgoState, actions: &mut Actions) {
// Iterate only CEX venues
for slot in 0..books.book_ct as usize {
let vid = books.venue_id_at(slot);
if is_cex(vid) {
let book = books.book_at_slot(slot);
// ... CEX-specific logic
}
}
// Count venue types
let cex_count = books.cex_count();
let dex_count = books.dex_count();
// Direct venue lookup
if let Some(binance_book) = books.book_for_venue(VENUE_BINANCE) {
// ... use Binance depth
}
}
```
## VenueBooks
`VenueBooks` provides full 20-level depth from every connected venue, plus a merged cross-venue book. Available in `on_nbbo()` for multi-venue algos.
### Fields
| `merged` | `L2Book` | Cross-venue merged depth (same-price levels aggregated) |
| `book_ct` | `u8` | Number of venues with depth data |
| `venue_ids` | `[u8; 16]` | VenueId per slot (same order as `NbboSnapshot`) |
| `books` | `[L2Book; 16]` | Per-venue L2Book per slot |
### Methods
```rust
// Look up a specific venue's book by VenueId constant
books.book_for_venue(VENUE_KRAKEN) -> Option<&L2Book>
// Direct slot access (faster, no scan)
books.book_at_slot(0) -> &L2Book
books.venue_id_at(0) -> u8
// Check if a venue has depth data
books.has_depth_for(VENUE_DEX_ARB) -> bool
// Count by venue type
books.cex_count() -> usize
books.dex_count() -> usize
```
### Using the Merged Book
The `merged` field aggregates depth from all venues. Same-price levels from different venues have their sizes summed. Levels are sorted (bids descending, asks ascending) and truncated to 20 per side.
```rust
// Use merged book for reference pricing
let mid = books.merged.mid_px_1e9();
let spread = books.merged.spread_bps();
// Total visible liquidity across all venues
let total_bid_depth: u64 = (0..books.merged.bid_ct as usize)
.map(|i| books.merged.bids[i].sz_1e8)
.sum();
```
## PoolBooks
`PoolBooks` gives per-pool granularity for DEX liquidity. While `VenueBooks` gives per-chain depth (aggregated across all pools on a chain), `PoolBooks` breaks it down to individual Uniswap V3, Curve, Raydium, etc. pools. Available at WASM offset `0x13000`, read on-demand during `on_nbbo()`.
### Fields
| `pool_ct` | `u8` | Number of active pool slots (0..32) |
| `metas` | `[PoolMeta; 32]` | Pool identity per slot |
| `books` | `[L2Book; 32]` | Per-pool L2Book per slot |
### PoolMeta
| `address` | `[u8; 32]` | Pool contract address (EVM: first 20 bytes, Solana: all 32) |
| `pair_index` | `u16` | For multi-asset pools like Curve (0 for standard 2-token pools) |
| `fee_bps` | `u16` | Pool fee in basis points (e.g., 5 = 0.05% for Uniswap V3) |
| `venue_id` | `u8` | Chain identifier (`VENUE_DEX_ARB`, `VENUE_DEX_SOL`, etc.) |
| `protocol_id` | `u8` | Protocol: 1=uniswap_v2, 2=uniswap_v3, 3=curve, 4=balancer_v2, 5=aerodrome, 6=velodrome, 7=camelot, 8=raydium_clmm, 9=orca_whirlpool |
### Methods
```rust
// Look up a pool by contract address and pair index
pool_books.book_for_pool(&address, pair_index) -> Option<&L2Book>
// Direct slot access (faster, no scan)
pool_books.book_at_slot(0) -> &L2Book
pool_books.meta_at_slot(0) -> &PoolMeta
```
### Example: Pool-Level Analysis
```rust
fn on_nbbo(&mut self, nbbo: &NbboSnapshot, books: &VenueBooks, state: &AlgoState, actions: &mut Actions) {
// Read pool books from WASM memory
let pool_books = unsafe { &*(algo_sdk::POOL_BOOKS_WASM_OFFSET as *const PoolBooks) };
// Compare fee tiers across pools
for i in 0..pool_books.pool_ct as usize {
let meta = pool_books.meta_at_slot(i);
let book = pool_books.book_at_slot(i);
if meta.fee_bps <= 5 && book.spread_bps() < 10 {
// Low-fee pool with tight spread — good for execution
}
}
}
```
## NbboSnapshot
The `NbboSnapshot` is the core data structure for multi-venue algos. It contains:
- **Global NBBO**: Best bid/ask price and size across all venues
- **Venue identification**: Which venue has the best bid, which has the best ask
- **Per-venue BBO**: Individual bid/ask for each connected venue (struct-of-arrays layout for cache efficiency)
- **Staleness tracking**: Milliseconds since last update per venue — reject stale data before trading
**Important index convention:**
- `nbbo_bid_venue` / `nbbo_ask_venue` are **array slot indices** (0..venue_ct-1), not VenueIds
- Use `venue_ids[nbbo_bid_venue]` to get the actual VenueId for order routing
- `Action.venue_id` takes the **VenueId value**, not the array slot
**Helper methods:**
- `nbbo_spread_bps()` — spread in basis points (negative = crossed market)
- `is_crossed()` — true when best bid > best ask (arb opportunity)
- `is_venue_stale(slot, max_ms)` — true when venue data is older than `max_ms`
- `slot_for_venue(venue_id)` — find the array slot for a given VenueId
- `best_bid_venue_id()` — the VenueId (not slot) of the best bid venue
- `best_ask_venue_id()` — the VenueId (not slot) of the best ask venue
## Order Routing
Single-venue algos use `actions.buy()` / `actions.sell()` — orders go to the algo's assigned venue.
Multi-venue algos use venue-targeted methods:
```rust
actions.buy_on(venue_id, order_id, qty, price); // Limit buy on specific venue
actions.sell_on(venue_id, order_id, qty, price); // Limit sell on specific venue
actions.ioc_buy_on(venue_id, order_id, qty, price); // IOC buy on specific venue
actions.ioc_sell_on(venue_id, order_id, qty, price); // IOC sell on specific venue
```
Set `venue_id = 0` for default venue routing (same as single-venue behavior).
## DEX Depth: How It Works
DEX order books are **synthetic** — constructed by probing AMM liquidity pools, not from a traditional order book. Understanding the methodology helps you use DEX depth data correctly.
### Construction
DEX depth is built by simulating swaps at 16 discrete USD sizes:
$10, $25, $50, $100, $250, $500, $750, $1K, $2.5K, $5K, $10K, $25K, $50K, $100K, $250K, $500K
Each probe returns an effective price at that size, which is converted into a bid/ask level. The result is a 20-level `L2Book` that approximates the continuous AMM curve as discrete levels.
### Gas Adjustment
All DEX prices are **gas-adjusted**: they include estimated swap gas cost at the current base fee. This means the prices you see already account for execution costs — a bid of $1000 on DEX means $1000 *after* gas.
### Routing
- **Below $10K**: Single best-pool routing (lowest-impact single pool)
- **Above $10K**: Greedy multi-pool split to reduce price impact across multiple pools
### Accuracy
DEX depth is validated every 10th emission cycle:
- **Suppressed** if the $100 probe has >25bps error vs. on-chain simulation
- **Suppressed** if the $10K probe has >75bps error
- When suppressed, the last valid book is held (no stale data pushed)
### Staleness
DEX data updates more slowly than CEX data. The `venue_update_ms` field in `NbboSnapshot` tracks freshness:
- **CEX** staleness threshold: ~10% outlier tolerance
- **DEX** staleness threshold: ~25% outlier tolerance (wider due to block times)
Use `nbbo.is_venue_stale(slot, max_ms)` to gate on freshness before trading DEX venues.
### DEX vs CEX in Your Algo
```rust
fn on_nbbo(&mut self, nbbo: &NbboSnapshot, books: &VenueBooks, state: &AlgoState, actions: &mut Actions) {
// Only trade CEX venues with tight spread
for slot in 0..nbbo.venue_ct as usize {
let vid = nbbo.venue_ids[slot];
if is_cex(vid) && !nbbo.is_venue_stale(slot, 2000) {
// CEX venue is fresh — safe to trade
}
}
// Use DEX depth for reference/hedging but with wider staleness tolerance
if let Some(dex_book) = books.book_for_venue(VENUE_DEX_ARB) {
if books.has_depth_for(VENUE_DEX_ARB) {
let dex_mid = dex_book.mid_px_1e9();
// Compare DEX mid vs CEX mid for cross-venue signal
}
}
}
```
## Fixed-Point Format
All prices and quantities use fixed-point integers for deterministic, allocation-free arithmetic:
- **Prices:** `px_1e9` — multiply by 10^9 (e.g., $50,000.00 = `50_000_000_000_000`)
- **Quantities:** `qty_1e8` — multiply by 10^8 (e.g., 1.0 BTC = `100_000_000`)
## Examples
See [`examples/`](examples/) for complete working algos:
- **mm-algo** — Single-venue market maker with spread-based quoting
- **arb-algo** — Multi-venue arbitrage with crossed-market detection and staleness gating
- **test-algo** — Minimal algo for testing and validation
- **speed-test-algo** — Performance benchmarking
- **xrp-5usd-mm** — XRP-specific market maker
## Backtesting
Build your algo to WASM and run it against recorded market data:
```bash
# Single symbol
sequence backtest BTC-USD --start 2026-03-01 --end 2026-03-08
# Multi-symbol portfolio
sequence backtest --symbols BTC-USD,ETH-USD,SOL-USD --start 2026-03-01 --end 2026-03-08
```
Or call `sim-engine` directly:
```bash
sim-engine run --wasm target/wasm32-unknown-unknown/release/my_algo.wasm \
--symbol BTC-USD --start 2026-03-01 --end 2026-03-08 --capital 100000
```
### Multi-Symbol Portfolio Mode
When using `--symbols`, events from all symbols are merged into a single timeline. Your algo receives `L2Book` updates with `symbol_id` set so you can distinguish which instrument updated:
```rust
fn on_book(&mut self, book: &L2Book, state: &AlgoState, actions: &mut Actions) {
match book.symbol_id {
1 => self.handle_btc(book, state, actions),
2 => self.handle_eth(book, state, actions),
_ => {}
}
}
```
Symbol IDs are assigned in order: the first symbol in `--symbols` gets ID 1, the second gets ID 2, etc.
Fills and rejects also carry symbol context (v0.4+):
```rust
fn on_fill(&mut self, fill: &Fill, state: &AlgoState) {
match fill.symbol_id() {
1 => self.btc_position += fill.qty_1e8,
2 => self.eth_position += fill.qty_1e8,
_ => {}
}
}
```
**Note:** In multi-symbol mode, `AlgoState` shows portfolio-level aggregates (total position, total PnL across all symbols). Your algo must track per-symbol state internally using `symbol_id()` on books and fills.
### Realism Controls
The sim engine models market microstructure effects that can be enabled via environment variables. All are **off by default** for backward compatibility:
| Adverse selection | `MAKER_ADVERSE_ENABLED=true` | Toxic trade detection — probabilistic fill skip + price penalty on maker fills |
| Vol-conditioned latency | `LATENCY_VOL_MU_SCALE=0.3` | Latency increases during high-volatility periods |
| Queue cancel attrition | `QUEUE_CANCEL_DECAY_RATE=0.5` | Queue ahead of you decays over time from cancellations |
| Self-impact | `SELF_IMPACT_ENABLED=true` | Your orders change the visible book depth |
For MM backtests, enabling all features provides more realistic P&L estimates:
```bash
MAKER_ADVERSE_ENABLED=true SELF_IMPACT_ENABLED=true QUEUE_CANCEL_DECAY_RATE=0.5 \
sequence backtest BTC-USD --start 2026-03-01 --end 2026-03-08
```
### Calibration
Tune sim parameters against historical data:
```bash
sim-engine calibrate --symbol BTC-USD --venue kraken --target queue
sim-engine calibrate --symbol BTC-USD --venue kraken --target maker-adverse-fill-rate
sim-engine calibrate --symbol BTC-USD --venue kraken --target maker-adverse-penalty
```
## Migrating from v0.2
v0.3 adds **per-venue depth** alongside the existing merged 20-level L2Book.
Previously, multi-venue algos received a single merged L2Book aggregating all venues.
Now `VenueBooks` gives access to both the merged view and individual venue order books.
v0.3 changes the `MultiVenueAlgo` trait signature:
```rust
// v0.2
fn on_nbbo(&mut self, nbbo: &NbboSnapshot, book: &L2Book, state: &AlgoState, actions: &mut Actions);
// v0.3
fn on_nbbo(&mut self, nbbo: &NbboSnapshot, books: &VenueBooks, state: &AlgoState, actions: &mut Actions);
```
The old merged `L2Book` is still available as `books.merged`. To migrate:
1. Change `book: &L2Book` to `books: &VenueBooks` in your `on_nbbo` signature
2. Replace any `book.` references with `books.merged.` (e.g., `book.mid_px_1e9()` becomes `books.merged.mid_px_1e9()`)
3. Optionally, start using per-venue depth via `books.book_for_venue(VENUE_*)` for smarter sizing
Pre-compiled v0.2 WASM binaries continue to work without recompilation.
## License
Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or [MIT License](LICENSE-MIT) at your option.