A quantitative trading environment built in Rust.
Event-driven, channel-based, and designed for modular strategy execution across multiple exchanges.
Maximizes runtime efficiency through static dispatch and promotes scalability with Heterogeneous Lists (HList) for strategy registration.
At its core: One unified framework for multiple exchanges, multiple strategies, zero runtime boxing.
Explore state-of-the-art example usages, architecture walkthroughs, and community Q&A—no need to run it, just see how strategies and data flows are structured.
👉 Join the discussion & explore examples:
GitHub Discussions – SOTA Usages
extrema_infra without an external Python service.AltTensor is the common dense tensor payload for feature input and model output.Predictions return asynchronously to Rust for signal generation and order execution.
Unified Exchange Abstraction
Market enum and data structs.Strategies can consume unified types while still handling exchange-specific capabilities where needed.
Broadcast-based Data Distribution
Strategy modules can narrow their runtime subscriptions with EventMask.
Static Efficiency
Box<dyn Strategy> and dynamic dispatch at the strategy-list boundary.Unified REST & WS interfaces with pre-converted data.
Channel-based Concurrency

Extrema Infra can model signal generation, model inference, allocation, portfolio mediation, and execution orchestration as independent tasks or strategy modules. Each module owns local state and communicates through async channels instead of directly sharing mutable state.
flowchart TB
EXS["N Exchanges
Binance / OKX / Gate / Hyperliquid"]
subgraph RT["Extrema Infra runtime"]
direction TB
subgraph INGEST["WS ingestion tasks"]
direction LR
PUB["Public market WS
trade / book / price"]
ACC["Account WS
position / order / fill"]
end
subgraph SIGS["N strategy signal modules"]
direction TB
S1["Signal A"]
S2["Signal B"]
S3["Signal N"]
end
FEAT["Feature stream
AltTensor"]
subgraph MODELS["N model strategy modules"]
direction TB
M1["LightGBM
Python / ZMQ"]
M2["ONNX
Rust"]
M3["Model N"]
end
MPRED["Model preds
AltTensor"]
ALLOC["Allocator modules
preds + price + account state"]
PM["Portfolio mediator
risk / constraints / OMS"]
PLAN["Order planner
slice / net / reduce-only / route"]
subgraph ORDERS["N order tasks"]
direction TB
O1["Account A"]
O2["Account B"]
O3["Account N"]
end
CMD["Strategy Cmd Path
optional WS order route"]
end
EXS -->|market stream| PUB
EXS -->|account stream| ACC
PUB -->|market events| S1
PUB -->|market events| S2
PUB -->|market events| S3
PUB -->|price events| ALLOC
S1 --> FEAT
S2 --> FEAT
S3 --> FEAT
FEAT --> M1
FEAT --> M2
FEAT --> M3
M1 --> MPRED
M2 --> MPRED
M3 --> MPRED
MPRED -->|model preds| ALLOC
ACC -->|positions / fills| ALLOC
ACC -->|order reports| PM
ALLOC -->|allocator intent| PM
PM --> PLAN
PLAN -->|order instruction| O1
PLAN -->|order instruction| O2
PLAN -->|order instruction| O3
O1 -->|async REST order| EXS
O2 -->|async REST order| EXS
O3 -->|async REST order| EXS
O1 -.->|WS order via command handle| CMD
O2 -.->|WS order via command handle| CMD
O3 -.->|WS order via command handle| CMD
CMD -.->|WS order command| EXS
ACC -.->|state feedback| PM
Signal, model, allocator, and portfolio-mediator components can all be Strategy Modules. The runtime composes them with websocket tasks, scheduler tasks, model-prediction tasks, command handles, and account-bound order tasks.
Traditional frameworks force strategies into homogeneous containers (e.g., Vec<Box<dyn Strategy>>), which means:
vtable lookups). With HList:
Strategy trait can be registered. Box<dyn Strategy> at the module-list boundary.| Aspect | Traditional Vec<Box<dyn Trait>> |
HList-based Extrema Infra |
|---|---|---|
| Dispatch | Dynamic (runtime vtable) |
Static (compile-time inlined) |
| Type Safety | Runtime only | Compile-time enforced |
| Performance | Extra indirection, heap alloc | Zero overhead, no heap alloc |
| Compile-time Checking | Limited | Full (trait bounds enforced) |
on_trade, on_candle, on_lob.EventMask lets modules subscribe only to callbacks they use.For a generic end-to-end wiring guide with scheduler, websocket, account-stream, and multi-module examples, see docs/usage.md.
Instrument naming conventions:
The extrema_infra crate provides the core traits to implement trading strategies:
Strategy
Entry point of your strategy. Defines how it executes and spawns tasks.
EventHandler
handle asynchronous model prediction events.
CommandEmitter
Used to initialize and register command handles for communication with tasks.
A minimal strategy must implement at least Strategy + CommandEmitter + EventHandler.
extrema_infra supports local ONNX inference through AltTaskType::ModelPreds(ModelRunner::Onnx(...)).
Enable the model_onnx feature, or model_runner / all, to make this backend available.
AltTensor feature payload and receive an AltTensor prediction payload.output_index; otherwise the runner picks the first decodable tensor output.You can initialize the runner in two ways:
.onnx path.{
"model_path": "models/demo.onnx",
"model_name": "demo_model",
"output_index": 0
}
The JSON config supports:
model_path: required, relative or absolute path to the ONNX filemodel_name: optional, added into prediction metadataoutput_index: optional, useful for multi-output models such as classifier label + probability outputsAltTensor ContractAltTensor is a generic dense tensor carrier:
data: row-major / C-order flattened Vec<f32>shape: original tensor shape before flatteningdata.len() must equal the product of shapeTypical examples:
shape=[1, 4], data=[f1, f2, f3, f4]shape=[1, assets, features]shape=[1, 1, 3, 3]shape=[1, 1]shape=[1, classes]When exporting from PyTorch, the intended semantics are equivalent to:
tensor = tensor.to(torch.float32).contiguous()
data = tensor.view(-1).tolist()
shape = list(tensor.shape)
These traits apply only to LOB-based exchanges (Binance, OKX, Hyperliquid, etc.)
For connecting to exchanges, you need to implement these traits for each exchange client:
LobWebsocket
Defines how to build subscription/connect messages for websocket streams.
MarketLobApi = LobPublicRest + LobPrivateRest
This framework relies on rustls for secure REST and WebSocket connections
(e.g. via reqwest and tokio-tungstenite).
Starting with rustls v0.23, the TLS crypto backend (e.g. aws-lc-rs or ring)
must be explicitly selected by the final binary.
Before using any TLS-enabled functionality (REST / WebSocket), the executable must install a default CryptoProvider:
#[tokio::main]
async fn main() {
rustls::crypto::aws_lc_rs::default_provider()
.install_default()
.expect("failed to install rustls crypto provider");
// start tokio runtime, env builder, etc.
}
Install the latest published crate:
cargo add extrema_infra --features all
In your strategy Cargo.toml:
[package]
name = "strategy"
version = "0.1.0"
edition = "2024"
[dependencies]
extrema_infra = { version = "0.1", features = ["all"] }
# Enable exchange clients explicitly when needed.
# extrema_infra = { version = "0.1", features = ["hyperliquid", "okx"] }
# extrema_infra = { version = "0.1", features = ["lob_clients"] }
# For local development.
# extrema_infra = { path = "../extrema_infra", features = ["all"] }
# Or use the latest default branch directly.
# extrema_infra = { git = "https://github.com/Lqz13Th/extrema_infra", features = ["all"] }
# Tokio async runtime
tokio = { version = "1.52", features = ["full"] }
# TLS / Cryptography
rustls = { version = "0.23", features = ["aws-lc-rs"] }
# Logging
tracing = "0.1"
tracing-subscriber = "0.3"
Then in main.rs:
```rust,no_run use std::{sync::Arc, time::Duration}; use tracing::info;
use extrema_infra::prelude::*;
struct EmptyStrategy { registry: Arc, }
impl EmptyStrategy { fn new() -> Self { Self { registry: Arc::new(CommandRegistry::default()), } } }
impl Strategy for EmptyStrategy { async fn initialize(&mut self) { info!("[EmptyStrategy] Executing..."); } }
impl CommandEmitter for EmptyStrategy { fn command_init(&mut self, registry: Arc) { self.registry = registry; info!("[EmptyStrategy] Command channel initialized"); }
fn command_registry(&self) -> Arc { self.registry.clone() } }
impl EventHandler for EmptyStrategy { async fn on_schedule(&mut self, msg: InfraMsg) { info!("[EmptyStrategy] AltEventHandler: {:?}", msg); } }
async fn main() { tracing_subscriber::fmt::init(); info!("Logger initialized");
let alt_task = AltTaskInfo { alt_task_type: AltTaskType::TimeScheduler(Duration::from_secs(5)), chunk: 1, task_base_id: None, };
let env = EnvBuilder::new() .with_board_cast_channel(BoardCastChannel::default_alt_event()) .with_board_cast_channel(BoardCastChannel::default_scheduler()) .with_task(TaskInfo::AltTask(Arc::new(alt_task))) .with_strategy_module(EmptyStrategy::new()) .build();
env.execute().await; } ```
For a practical implementation, see the complex strategy example.
Samples only the data needed by the fast path.
Supporting tasks
Latency-sensitive logic can be decomposed into multiple tasks, with each task handling only a subset of instruments for maximum efficiency.
This project is licensed under the Apache 2.0 license.
$ claude mcp add extrema_infra \
-- python -m otcore.mcp_server <graph>