tradectl-lab
tradectl-lab is a local dashboard that bundles the backtest, sweep, and live-monitor experience into a single binary. It runs entirely on your machine — no platform account required — and serves a Next.js frontend over an Axum API on port 9200.
Coming soon: tradectl lab
The lab will become a CLI subcommand of the main tradectl binary. After the next release you'll be able to launch it with:
tradectl lab # starts the lab on http://localhost:9200Until then, build and run the standalone binary as described below.
What it gives you
The lab is structured around four pages, all reading from the same SQLite database (~/.tradectl/lab.db).
| Page | Purpose |
|---|---|
| Dashboard | Stats and recent activity across backtests, sweeps, and saved configs |
| Backtest | Single-run UI — pick a strategy + data file, set params, see equity curve, drawdown, and trade log |
| Optimize | Parameter sweeps — define ranges, see variant count estimate, watch ranked results stream in via SSE |
| Data | Browse ~/.tradectl/data/, preview ticker/trade files, prepare raw logs into the optimized binary format |
| Configs | Saved param sets (CRUD) — quick-link to backtest or optimize |
| Results | History of all backtests and sweeps, with drill-down into trades and equity points |
| Monitor | Live bot dashboard — works for paper or live runs |
How /monitor works
The Monitor page is a WebSocket client that connects directly to the bot's MonitorBroadcaster on port 9100 (default). The bot streams ticker snapshots, fills, position state, and (when shadow optimization is enabled) per-variant summaries. The lab backend is not in the loop — the page talks to the bot directly.
This means a single lab install can monitor any bot you have running:
- Local paper-trading bot — open
http://localhost:9200/monitor - Local live bot — same URL, the wire format is identical
- Remote bot — change the URL in the page's "Bot URL" field to point at the remote
MonitorBroadcaster
Plugin discovery
The lab scans ~/.tradectl/lib/ on startup for .so / .dylib strategy plugins. Each candidate is probed by spawning a subprocess (tradectl-lab --probe <path>) so a plugin that panics during initialization doesn't crash the lab. The probe loads the plugin, calls params_schema(), and outputs the schema as JSON.
Sweep execution
Sweeps run in two modes:
- Blocking (
POST /api/sweep) — the lab spawns a subprocess (tradectl-lab --sweep) that loads the plugin, generates the Cartesian product of parameter ranges, runs the batch strategy, and writes results back. Subprocess isolation means a panic in the strategy doesn't take down the lab. - SSE streaming (
POST /api/sweep/stream) — same scoring and DB pipeline, but runs in-process so the UI can stream phase updates over Server-Sent Events.
Variants are ranked by compute_score(pnl%, trade_count, max_dd%, min_trades).
Database
A single SQLite file at ~/.tradectl/lab.db (WAL mode), shared with the CLI. Running tradectl backtest or tradectl sweep writes into the same database, so anything you run from the terminal shows up in the lab UI immediately.
| Table | Holds |
|---|---|
backtests | Single-run jobs (strategy, data file, params JSON, all metrics) |
trades | Individual fills (polymorphic FK to backtest or sweep_result) |
equity_points | Balance over time (polymorphic FK) |
sweeps | Sweep jobs (config JSON: fixed + ranges, variant counts) |
sweep_results | Per-variant results (params JSON, metrics, score, rank) |
configs | Saved parameter sets |
benchmarks | Reserved for future use |
Build & run (standalone)
Until tradectl lab ships, the lab is a standalone binary. The web frontend is a static export served by the Rust backend.
cd lab
cargo build --release # backend
cd web && npm install && npm run build && cd .. # frontend (static export to web/out/)
./target/release/tradectl-lab # serves on http://localhost:9200Override the port with LAB_PORT:
LAB_PORT=9300 ./target/release/tradectl-labRelationship to other components
tradectl backtest / tradectl sweep tradectl run (paper or live)
│ │
▼ ▼
~/.tradectl/lab.db ◄── shared SQLite ── MonitorBroadcaster :9100
▲ ▲
│ │
lab UI ────────── Axum API :9200 ────► /monitor (WS client)The lab UI reads everything that the CLI writes. The /monitor page bypasses the database entirely and connects to the bot's broadcaster directly.
Notes & gotchas
- No
tradectl labyet — coming in the next CLI release. For now, run the binary directly fromlab/target/release/. - Plugin filenames — Cargo converts dashes to underscores for
cdyliboutput (shot→libshot.so). The lab handles the mapping. - Plugin panics are isolated — the probing subprocess exits, and the strategy is excluded from the dropdown but the lab keeps running.
- Static export frontend — the Next.js bundle is built once with
next build && next exportand served fromweb/out/. Changes to React code require a rebuild. - Shared DB locking — both lab and CLI use SQLite WAL mode; concurrent reads are fine. Don't open
lab.dbin another writer (e.g., a third-party SQLite GUI in write mode) while the lab is running.
