Skip to content

Rust SDK Overview

The tradectl SDK (tradectl-sdk) provides the building blocks for writing trading strategies — events, the Strategy trait, the Action enum, profit math, and the FFI plumbing that lets the CLI and lab load strategies as dynamic libraries.

Strategies are compiled as cdylibs. The CLI loads them via libloading. The current ABI version is STRATEGY_ABI_VERSION = 5 — strategies built against an older SDK are rejected at load.

Installation

tradectl init sets this up automatically. To add it manually:

toml
[dependencies]
tradectl-sdk = "0.1"

[lib]
crate-type = ["cdylib"]

Core Types

Events

TypePurpose
TickerEventBest bid/ask snapshot (#[repr(C)] for zero-copy)
TradeEventAggregated trade (price, qty, timestamp, taker side)
KlineEventOHLCV candle with interval
DepthEventL2 order book update (when subscribed)
MarketEventSum type wrapping any of the above
FillEventFill notification — entry vs exit, partial flag, exit ID

Strategy primitives

TypePurpose
Strategy traiton_ticker, on_trade, on_fill, name, describe, params_schema, monitor_snapshot, session_state, session_reset
Action enumHold, PlaceEntry, EditEntry, CancelEntry, SetExits, AddExit, UpdateExit, RemoveExit, CloseAll
FillResponseList of follow-up Actions + notify flag
ExitOrderid, price, size, kind (Limit/Stop), delay_ms
StrategyContextpositions, balance, PnL, trade count, depth, volume profile, can_enter gate
PositionInfoAccumulated position: avg_entry, quantity, total_entered, entry_count
SideLong, Short
MarketTypeSpot, Linear, Inverse
MonitorSnapshotOptional metrics surface for the live monitor UI

See Strategy Trait for full struct definitions and the new exit-management model.

Parameters

TypePurpose
ParamsHashMap<String, serde_json::Value> — strategy params from JSON config
ParamDefSchema entry: key, description, default, min, max, step

Params is JSON-typed, so it accepts numbers, strings, arrays, and nested objects — Params::get(key, default) parses to the requested type.

Loading & registration

MacroUse
declare_strategy!Exports tradectl_strategy() C-ABI symbol — required for every plugin
declare_batch_strategy!Same as above, plus a BatchFactory for sweep/shadow batch mode

Indicators

Built into tradectl-indicators (re-exported from the SDK as tradectl_sdk::indicators). All implement the Indicator trait with O(1) streaming updates: .update(value), .value(), .ready().

IndicatorNotes
SmaSimple Moving Average
EmaExponential Moving Average (k = 2 / (period + 1))
RsiRelative Strength Index, Wilder's smoothing
MacdEMA(12)/EMA(26) with EMA(9) signal — .value(), .signal(), .histogram()
BollingerBands.value() (mid), .upper(), .lower()
AtrAverage True Range — .update_hlc(high, low, close) for accuracy
StdDevRolling standard deviation
VwapVolume-weighted average — .update_pv(price, vol), .reset() at session boundaries

See Indicators for examples.

MarketAdapter

Unified async interface for all 7 exchanges plus paper and replay adapters. Used by the live runner and the backtest engine alike.

rust
#[async_trait]
pub trait MarketAdapter: Send + Sync {
    async fn place_order(&self, request: &OrderRequest) -> ExchangeResult<Order>;
    async fn cancel_order(&self, symbol: &str, order_id: &str) -> ExchangeResult<()>;
    async fn edit_order(&self, symbol: &str, order_id: &str, side: OrderSide,
                        price: f64, quantity: Option<f64>) -> ExchangeResult<Order>;
    async fn fetch_order(&self, symbol: &str, order_id: &str) -> ExchangeResult<Option<Order>>;
    async fn fetch_open_orders(&self, symbol: &str) -> ExchangeResult<Vec<Order>>;
    async fn get_balance(&self) -> ExchangeResult<f64>;
    async fn set_leverage(&self, symbol: &str, leverage: f64) -> ExchangeResult<()>;
    fn calculate_profit(&self, order: &Order) -> ProfitResult;
    // ...30+ methods total: pair info, market data callbacks, depth, profit, IDs
}

All methods take &self. Implementations use interior mutability (Mutex, RwLock) so the adapter can be shared as Arc<dyn MarketAdapter> across strategies and the runner without an outer mutex. This unlocks parallel REST calls.

Profit calculation

Standalone pure functions (not methods on the adapter):

rust
pub fn calculate_linear_profit(side: Side, entry: f64, exit: f64, qty: f64,
                               maker_fee: f64, taker_fee: f64) -> ProfitResult;
pub fn calculate_inverse_profit(side: Side, entry: f64, exit: f64, qty: f64,
                                maker_fee: f64, taker_fee: f64) -> ProfitResult;
pub fn calculate_spot_profit(entry: f64, exit: f64, qty: f64,
                             maker_fee: f64, taker_fee: f64) -> ProfitResult;

Score function

compute_score(pnl_pct, trade_count, max_dd_pct, min_trades) — used by sweeps and shadow ranking:

score = pnl% × min(trades / min_trades, 1.0) / (1 + max_dd%)

BatchStrategy

The SoA (Structure-of-Arrays) trait that backs sweeps and shadow optimization. ~1000× faster than running individual strategy instances per variant.

rust
pub trait BatchStrategy: Send {
    fn process_ticker(&mut self, ticker: &TickerEvent);
    fn check_trade(&mut self, trade: &TradeEvent);
    fn force_close_all(&mut self, bid_price: f64);
    fn results(&self) -> Vec<BatchResult>;
    // ...
}

See Optimization and Shadow Optimization.

Errors

rust
pub enum ApiErrorKind {
    // 17 variants, classified via .is_fatal(), .is_retryable(),
    // .is_silent(), .is_persistent()
    InsufficientMargin, MinNotional, RateLimit, NotFound,
    InvalidSignature, Network, Timeout, /* ... */
}

The runner uses these classifications to decide whether to retry, pause, or stop a strategy. 3+ persistent errors (margin/qty exceeded) within 60 s shut the strategy down across all symbols.

tradectl — Automate Crypto Trading