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

json
{
  "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

json
{ "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:

json
{
  "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:

CostModeled?How
Venue trading feesyesTaker rate per venue, applied to every fill (freqtrade fee config / Nautilus fill model)
Superior builder fee (Hyperliquid)yesThe same 0.01% your live deployment pays — backtest and live see identical costs
SlippagenoFills at candle prices; thin markets and large sizes will do worse live
Funding payments (perps)noNot 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).