View Markdown

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.

GET/runtime/backtests/dataset#

Resolve the one latest immutable dataset release that can serve an exact market before creating a Nautilus backtest. Both parameters are required:

/runtime/backtests/dataset?venue=lighter&market=SOL-PERP.LIGHTER

json
{
  "venue": "lighter",
  "market": "SOL-PERP.LIGHTER",
  "backtest_ready": true,
  "dataset": {
    "manifest_uri": "gs://bucket/backtesting-data/lighter/releases/lighter-20260808T084050Z/catalog.json",
    "dataset_version": "lighter-20260808T084050Z",
    "bars": {
      "timeframe": "1m",
      "coverage_start": "2025-01-17T08:44:00Z",
      "coverage_end": "2025-02-14T00:00:00Z"
    },
    "funding": {
      "coverage_start": "2025-01-17T08:00:00Z",
      "coverage_end": "2026-07-31T00:00:00Z"
    }
  }
}

Only the latest published release is considered. The endpoint never falls back to an older release or returns a venue-wide catalog. When the current release cannot serve the requested market, it returns backtest_ready: false and an unavailability_reason. Use the returned manifest_uri, dataset_version, and bar coverage to create a reproducible backtest. venue supports lighter and polymarket; Polymarket market is its outcome-token instrument ID.

POST/runtime/backtests#

Request

json
{
  "framework": "freqtrade",
  "venue": "hyperliquid",
  "symbols": ["BTC/USDC:USDC"],
  "code": "class BtcRange(IStrategy):\n    …",
  "config": {
    "timeframe": "4h",
    "stake_amount": 100,
    "exchange": { "name": "hyperliquid", "pair_whitelist": ["BTC/USDC:USDC"] }
  },
  "range": { "from": "2026-01-01", "to": "2026-06-30" }
}

For Nautilus, copy the exact framework_symbols.nautilus alias into both top-level symbols and config.instrument_ids:

json
{
  "framework": "nautilus",
  "venue": "hyperliquid",
  "symbols": ["BTC-PERP.HYPERLIQUID"],
  "code": "from nautilus_trader.trading.strategy import Strategy\n\nclass BtcRange(Strategy):\n    …",
  "config": {
    "instrument_ids": ["BTC-PERP.HYPERLIQUID"],
    "trade_size_usd": 50
  },
  "range": { "from": "2026-03-01T00:00:00Z", "to": "2026-06-30T00:00:00Z" }
}

Freqtrade follows the same identity rule: top-level symbols and config.exchange.pair_whitelist both use framework_symbols.freqtrade. Sending a venue's canonical top-level symbol where the selected framework requires a different alias returns 400 invalid_request; there is no canonical-symbol fallback. Context and exact dataset lookups still use the top-level canonical symbol.

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. For Nautilus Lighter and Polymarket runs, check the exact-market dataset endpoint first; a range outside its bar coverage fails fast with data_unavailable. /context/datasets remains the broad catalog reference. 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.

For an account-owned legacy v2 Freqtrade backtest on Hyperliquid, this known-ID GET can return the persisted record only after it has reached the terminal finished or error state. A terminal legacy result contains only the summary fields that the previous API originally persisted; the Unified API does not synthesize missing canonical result fields. This compatibility does not expand list behavior or adopt active runtime ownership: legacy queued or running status, logs, cancellation, and other lifecycle actions remain unavailable through the Unified API.

DELETE/runtime/backtests/:id#

Delete a backtest and its runtime resources. The response returns the deleted backtest record; retrying the same delete is safe.

For an account-owned legacy v2 Hyperliquid Freqtrade backtest in terminal finished or error status, deletion tombstones the persisted record without contacting the retired runtime workload. Active queued or running legacy records and ambiguous records remain protected and return 409 unsupported_runtime_record.

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. Each logs entry uses the canonical <RFC3339Nano UTC timestamp> <message> format — this is 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).