Backtests
Validate a strategy against real history before it touches capital.
A backtest takes the same payload as a deployment plus a date range, runs it against the immutable datasets, and returns the result inline. The symmetry is deliberate: the strategy that backtested is byte-identical to the one you deploy.
POST/runtime/backtests
Request
{
"framework": "freqtrade",
"venue": "hyperliquid",
"code": "class BtcRange(IStrategy):\n …",
"config": { "timeframe": "4h", "stake_amount": 100, "pair_whitelist": ["BTC/USDC:USDC"] },
"range": { "from": "2026-01-01", "to": "2026-06-30" }
}
Response — 202
{ "id": "bt_01j8xw…", "status": "queued" }
Backtests are asynchronous — queue, poll, read. Typical runs finish in one to ten minutes depending on range and pair count. A range outside dataset coverage fails fast with data_unavailable and the covered window, so check /context/datasets first. Creates accept an Idempotency-Key header (kept 24h) — a retried create returns the original backtest instead of burning a second concurrency slot.
GET/runtime/backtests
List: ?status=, ?venue=, limit/cursor — same { "backtests": [...], "next_cursor": null } envelope as deployments.
GET/runtime/backtests/:id
Status and, once finished, the embedded result — one endpoint to poll:
{
"id": "bt_01j8xw…",
"status": "finished",
"release": "rel_2026-07-27T02",
"range": { "from": "2026-01-01", "to": "2026-06-30" },
"result": {
"profit_total_pct": 6.7,
"market_change_pct": -4.2,
"max_drawdown_pct": 8.1,
"trades": 41,
"win_rate": 0.56,
"profit_factor": 1.31,
"sharpe": 0.9,
"per_pair": [{ "symbol": "BTC/USDC:USDC", "profit_pct": 6.7, "trades": 41 }],
"equity_curve": [{ "t": "2026-01-01", "equity": 1.0 }, { "t": "2026-06-30", "equity": 1.067 }]
}
}
market_change_pct — what buy-and-hold did over the same window — ships in every result. A strategy that made 6.7% while the market fell 4.2% and one that made 6.7% while the market rose 30% are different findings; the response won't let you confuse them.
release pins the dataset snapshot where the venue has release machinery (Lighter, Polymarket): the run is reproducible, and the exact history it saw is auditable via /context/candles. On venues whose release pipeline is pending (Hyperliquid, Binance — see datasets), release is null: the backtest ran on the live backfill store, and a re-run after backfills extend may see more history.
equity_curve is daily-resolution (one point per UTC day, plus the final point); per-trade detail lives in the full result's trade list.
Cost model
What a result's numbers do and don't include — read this before trusting any profit_total_pct:
| Cost | Modeled? | How |
|---|---|---|
| Venue trading fees | yes | Taker rate per venue, applied to every fill (freqtrade fee config / Nautilus fill model) |
| Superior builder fee (Hyperliquid) | yes | The same 0.01% your live deployment pays — backtest and live see identical costs |
| Slippage | no | Fills at candle prices; thin markets and large sizes will do worse live |
| Funding payments (perps) | no | Not applied to holding periods — a long-hold perp strategy's real carry cost is invisible here; check /context/funding yourself |
A strategy that only survives on zero slippage and free funding is a backtest artifact. Paper mode is the next filter.
GET/runtime/backtests/:id/logs
The engine's run logs — where a strategy that produced zero trades explains itself (entry condition never met, pair data missing, indicator error at startup).
Statuses
queued → running → finished, or error with a structured reason. Results are retained for 12 months and listable. A backtest is a record you can cite (the leaderboard does exactly that).