Skip to content

Strategies

A strategy is a Rust struct that implements the Strategy trait. It receives market data events and decides when to open, close, or hold positions.

The Strategy Trait

rust
pub trait Strategy: Send {
    fn on_ticker(&mut self, ticker: &TickerEvent, ctx: &StrategyContext) -> Action;
    fn name(&self) -> &str;

    // Optional — have defaults
    fn on_trade(&mut self, trade: &TradeEvent, ctx: &StrategyContext) -> Action { Action::Hold }
    fn describe(&self) -> &str { "" }
    fn params_schema(&self) -> Vec<ParamDef> { vec![] }
    fn on_position_close(&mut self, close: &CloseInfo, ctx: &StrategyContext) { }
}

Only on_ticker and name are required. Everything else has a default.

TickerEvent

Each ticker contains the current best bid/ask from the exchange:

rust
#[repr(C)]
pub struct TickerEvent {
    pub bid_price: f64,
    pub bid_qty: f64,
    pub ask_price: f64,
    pub ask_qty: f64,
    pub timestamp_ms: u64,
}

The #[repr(C)] attribute enables zero-copy binary serialization for fast data loading.

StrategyContext

The context provides information about your current positions and account state:

rust
pub struct StrategyContext<'a> {
    pub timestamp_ms: u64,
    pub book: Option<&'a TickerEvent>,
    pub positions: &'a [PositionInfo],
    pub balance: f64,
    pub unrealized_pnl: f64,
    pub realized_pnl: f64,
    pub trade_count: usize,
}

pub struct PositionInfo {
    pub id: u64,
    pub side: Side,
    pub entry_price: f64,
    pub quantity: f64,
    pub unrealized_pnl: f64,
    pub entry_time: u64,
}

Actions

rust
pub enum Action {
    Hold,
    MarketOpen { side: Side, size: Option<f64> },
    LimitOpen { side: Side, price: f64, size: Option<f64> },
    ClosePosition { position_id: u64, reason: CloseReason },
    CloseAll,
    CancelPending,
}

pub enum Side { Long, Short }

pub enum CloseReason { TakeProfit, StopLoss, ForceClose }
  • Hold — do nothing
  • MarketOpen — open a position at market price (size: None uses configured default)
  • LimitOpen — place a limit order at a specific price
  • ClosePosition — close a specific position by ID with a reason
  • CloseAll — close all open positions
  • CancelPending — cancel all pending limit orders

declare_strategy! Macro

Every strategy must export itself via the declare_strategy! macro:

rust
tradectl_sdk::declare_strategy!("my_strategy", MyStrategy::new);

This generates the FFI entry point that the CLI and platform use to load your strategy as a dynamic library.

Params-Based Constructor

Strategies receive parameters through a Params map:

rust
pub struct MyStrategy {
    order_size: f64,
}

impl MyStrategy {
    pub fn new(params: &Params) -> Self {
        Self {
            order_size: params.get("order_size", 0.1),
        }
    }
}

Define the parameter schema for the dashboard and CLI:

rust
fn params_schema(&self) -> Vec<ParamDef> {
    vec![
        ParamDef {
            key: "order_size",
            description: "Order size in quote currency",
            default: 0.1,
            min: None,
            max: None,
            step: None,
        },
    ]
}

Managers

Strategies use composition over inheritance. Managers are injected as struct fields:

TPSLManager

Manages take-profit and stop-loss orders:

rust
use tradectl_sdk::*;

pub struct MyStrategy {
    tpsl: TPSLManager,
}

impl MyStrategy {
    pub fn new(params: &Params) -> Self {
        Self {
            tpsl: TPSLManager::new(0.02, 0.01), // 2% TP, 1% SL
        }
    }
}

KlineManager

Builds candles from tick data:

rust
pub struct MyStrategy {
    klines: KlineManager,
}

impl MyStrategy {
    pub fn new(params: &Params) -> Self {
        Self {
            klines: KlineManager::new(Duration::from_secs(60)), // 1m candles
        }
    }
}

RangeBarManager

Builds range bars (fixed price movement per bar):

rust
pub struct MyStrategy {
    bars: RangeBarManager,
}

impl MyStrategy {
    pub fn new(params: &Params) -> Self {
        Self {
            bars: RangeBarManager::new(10.0), // $10 range per bar
        }
    }
}

Position Close Callback

React to position closes (logging, state updates):

rust
fn on_position_close(&mut self, close: &CloseInfo, ctx: &StrategyContext) {
    // close.side, close.entry_price, close.close_price
    // close.profit_pct, close.profit_usd, close.reason
}

tradectl — Automate Crypto Trading