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
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| Flag | Description | Default |
|---|---|---|
-d, --data <PATH> | Prepared data file | required |
-r, --range <KEY=MIN:MAX:STEP> | Range to sweep (repeatable) | required |
-p, --param <KEY=VAL> | Fixed parameter (repeatable) | — |
--balance <FLOAT> | Initial balance | 10000 |
--leverage <FLOAT> | Leverage multiplier | 1.0 |
--taker-fee <FLOAT> | Taker fee | 0.0004 |
--maker-fee <FLOAT> | Maker fee | 0.0002 |
--slippage <FLOAT> | Slippage | 0.0001 |
--top <N> | Show top N results | 20 |
--min-trades <N> | Minimum trades to be eligible | 5 |
-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
- Backtests → New Optimization
- Select strategy, set base parameters
- Define ranges and steps
- Choose a ranking metric (PnL, Sharpe, Calmar, Score)
- Run — progress streams via WebSocket (
optimization:<id>:progress).
Ranking metrics
| Metric | Formula |
|---|---|
| Score (default) | pnl% × min(trades / min_trades, 1.0) / (1 + max_dd%) |
| PnL | Net profit % |
| Sharpe | Risk-adjusted return |
| Calmar | PnL / max drawdown |
| Win Rate | Percentage of winning trades |
| Profit Factor | Gross 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:
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!:
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, ranktradesandequity_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.
