Skip to content

Parameter 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. Backtests → New 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.