Skip to content

Optimization

Parameter optimization (sweeps) test a strategy across many parameter combinations to find the best configuration. Sweeps run on prepared historical data and rank variants by score.

Running a sweep

Via CLI

bash
tradectl sweep \
  -d data/prepared.bin \
  -r entryDistance=0.5:3.0:0.1 \
  -r takeProfit=0.3:1.5:0.1 \
  -p order_size=0.001 \
  -p leverage=5 \
  --balance 10000 \
  --taker-fee 0.0004 \
  --maker-fee 0.0002 \
  --top 20 \
  --min-trades 5 \
  -o results.json
FlagDescriptionDefault
-d, --data <PATH>Prepared data filerequired
-r, --range <KEY=MIN:MAX:STEP>Range to sweep (repeatable)required
-p, --param <KEY=VAL>Fixed parameter (repeatable)
--balance <FLOAT>Initial balance10000
--leverage <FLOAT>Leverage multiplier1.0
--taker-fee <FLOAT>Taker fee0.0004
--maker-fee <FLOAT>Maker fee0.0002
--slippage <FLOAT>Slippage0.0001
--top <N>Show top N results20
--min-trades <N>Minimum trades to be eligible5
-o, --output <PATH>Save results as JSON

Via lab

The lab provides an interactive UI for sweeps — define ranges, see the variant count estimator, and watch ranked results stream in via SSE. Same engine, same scoring.

Via dashboard

  1. BacktestsNew Optimization
  2. Select strategy, set base parameters
  3. Define ranges and steps
  4. Choose a ranking metric (PnL, Sharpe, Calmar, Score)
  5. Run — progress streams via WebSocket (optimization:<id>:progress).

Ranking metrics

MetricFormula
Score (default)pnl% × min(trades / min_trades, 1.0) / (1 + max_dd%)
PnLNet profit %
SharpeRisk-adjusted return
CalmarPnL / max drawdown
Win RatePercentage of winning trades
Profit FactorGross profit / gross loss

min_trades damps strategies with too few samples — a 100% win rate on 2 trades doesn't beat a 60% win rate on 200.

Batch engine

Sweeps use the BatchStrategy trait — a Structure-of-Arrays interface that evaluates many parameter sets in a single pass through the data:

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>;
    fn trial_count(&self) -> usize;
    fn reset(&mut self);
    fn estimated_ram_bytes(&self) -> usize;
}

Throughput is roughly ~1000× faster than running the same number of variants as individual Strategy + InMemoryExchange instances. The cost is implementation complexity — the strategy's state is split into flat arrays, one entry per variant.

To enable batch mode, register the plugin with declare_batch_strategy! instead of declare_strategy!:

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

Without batch mode, sweeps fall back to generic mode — works with any strategy, but ~1000× slower.

Sweep storage

The CLI writes results to ~/.tradectl/lab.db (shared with tradectl-lab):

  • sweeps — job metadata (strategy, data file, ranges, variant counts)
  • sweep_results — per-variant params, metrics, score, rank
  • trades and equity_points — drill-downs by (source_type='sweep_result', source_id=...)

Anything you run via tradectl sweep shows up in the lab UI immediately, and vice versa.

Live shadow optimization

For online optimization (alongside a running bot), see Shadow Optimization. Shadow uses the same BatchStrategy trait but runs in real time on live market data, with optional automatic promotion of winning variants.

tradectl — Automate Crypto Trading