Sequence/docs

NativeAlgo — Native Rust Strategies

WASM is the right boundary for third-party code. For first-party HFT — where Sequence (or your team) owns both the host and the strategy — the WASM sandbox is overhead you don't need. The NativeAlgo path compiles a strategy to a .so (Linux) or .dylib (macOS), loads it at the venue edge via libloading, and dispatches every callback through a single function-pointer call in a C-ABI vtable.

Note

Why it's faster. Dispatch cost on the bench harness in algo-loader-native: ~1.5 ns/callback, vs 500 ns – 2 µs for the WASM path. No fuel accounting, no linear-memory copy, no sandbox boundary — just (vtable.on_book)(instance, &book, &state, &features, &mut actions). See crates/sdk/algo-loader-native/benches/dispatch.rs.

Warning

NativeAlgo executes in-process with the venue edge. There is no sandbox: a panic or a wild pointer takes down the edge. Sequence reserves NativeAlgo for code we control end-to-end (compile, sign, audit). Use WASM strategies for anything that isn't first-party.


The contract: one trait, one macro

A NativeAlgo is an implementation of the same Algo trait the WASM path uses, plus a one-line macro that emits the C-ABI vtable:

rust
use algo_sdk::traits::Algo;
use algo_sdk::{Actions, AlgoState, Fill, L2Book, OnlineFeatures, Reject};
 
#[derive(Default)]
pub struct MyStrategy {
    next_id: u64,
    working_bid: u64,
}
 
impl Algo for MyStrategy {
    fn on_book(
        &mut self,
        book: &L2Book,
        _state: &AlgoState,
        _features: &OnlineFeatures,
        actions: &mut Actions,
    ) {
        // your logic here — write into `actions`, return.
    }
 
    fn on_fill(&mut self, _fill: &Fill, _state: &AlgoState) {}
    fn on_reject(&mut self, _reject: &Reject) {}
}
 
// This single line emits `extern "C" fn sequence_algo_vtable_v1() -> *const SequenceAlgoVtable`
// — the symbol `algo-loader-native` resolves via dlsym at load time.
algo_sdk::export_native_algo!(MyStrategy);

That's the full surface. The same trait, the same Actions buffer, the same L2Book — only the host-side dispatcher changes.


Cargo setup

A NativeAlgo lives in a cdylib crate that depends on algo-sdk:

toml
# Cargo.toml
[package]
name = "my-strategy"
version = "0.1.0"
edition = "2021"
 
[lib]
crate-type = ["cdylib"]
 
[dependencies]
algo-sdk = { version = "0.4", package = "sequence-algo-sdk", default-features = false, features = ["std"] }

Build the artifact:

bash
cargo build --release
# → target/release/libmy_strategy.so        (Linux)
# → target/release/libmy_strategy.dylib     (macOS)

The output file is the artifact the loader consumes. No additional packaging.


How the loader sees it

The host-side library is algo-loader-native. It opens the file, resolves the single exported symbol, validates the ABI version, and produces a NativeAlgoSlot that wraps one strategy instance:

rust
use algo_loader_native::NativeAlgoLoader;
 
let loader = NativeAlgoLoader::open("/var/lib/sequence/strategies/libmy_strategy.so")?;
let mut slot = loader.create_slot()?;
 
// Hot path — dispatched through the C-ABI vtable:
slot.on_book(&book, &state, &features, &mut actions);
slot.on_fill(&fill, &state);

The NativeAlgoSlot owns:

  • The opaque *mut AlgoInstance your create returned (the macro generates this from MyStrategy::default()).
  • A pointer to the 'static vtable inside the shared library.
  • An Arc<Library> that keeps the artifact mapped in process memory.

Drop order matters. When the slot is dropped, the macro-generated destroy runs before the Library Arc releases. Otherwise dropping the library first would unmap the destroy function pointer and segfault on the way out. The slot's Drop impl enforces this discipline.


Capabilities — declaring optional callbacks

The required callbacks are on_book, on_fill, on_reject. Everything else (on_nbbo, on_message, on_heartbeat, on_start, on_stop, on_pause, on_resume, on_subscribed, on_unsubscribed) is optional. The macro takes a capability list so the host knows which to actually wire up:

rust
algo_sdk::export_native_algo!(MyStrategy, capabilities = [OnNbbo, OnHeartbeat, OnStart]);

The loader reads SequenceAlgoVtable::capabilities at open time and only prepares inputs for the callbacks you've declared. Skipping OnNbbo means the host never builds the VenueBooks snapshot for your slot.


ABI version + drift detection

The vtable carries an abi_version: u32 field. The loader compares it against algo_sdk::ffi::ABI_VERSION and refuses to open the artifact if they differ:

text
NativeAlgoError::AbiMismatch { got: 4, expected: 5 }

Treat any change to the structs in algo-contract (L2Book, Action, Fill, etc.) as an ABI break and bump the version. The compile-time assert!(size_of::<L2Book>() == 656) in algo-contract::book catches drift early.


Host callbacks (seq::*)

A strategy can call into the host during a callback — to subscribe to a new symbol, read or write a persistent parameter, look up its current position. These go through the SDK's seq module, which reads a thread-local installed by the macro at the top of every callback:

rust
impl Algo for MyStrategy {
    fn on_book(&mut self, book: &L2Book, _state: &AlgoState, _f: &OnlineFeatures, _a: &mut Actions) {
        if some_condition {
            // Add a symbol to my subscription set:
            let _ = algo_sdk::seq::subscribe("ETH-USDT");
        }
        let qty = algo_sdk::seq::get_position("BTC-USDT").unwrap_or(0);
        // …
    }
}

In no_std builds (rare for NativeAlgo) the seq::* helpers compile to no-ops that return a failure status. In normal builds they work transparently.


Dispatch latency, measured

The bench harness crates/sdk/algo-loader-native/benches/dispatch.rs measures empty on_book callback overhead:

text
native vtable dispatch    p50 ≈ 1.5 ns       (function-pointer call + 4 ref loads)
WASM dispatch (algo-rt)   p50 ≈ 500 ns – 2 µs (linear memory marshalling + fuel + sandbox)

That ~1000× headroom is what lets a NativeAlgo run on the venue edge's hot path without budget review.


When to reach for NativeAlgo

SituationChoice
Third-party code; sandbox required; deployment via your build pipelineWASM Algo
First-party HFT; latency below 10 µs end-to-end matters; you sign every binary that landsNativeAlgo
Research strategy you want to iterate on quickly in PythonHosted Python (when available)

Same trait, three deployment targets. Promote between them without changing strategy code.


See also