---
title: "Backtest a strategy"
method: GET
path: "/backtest"
tags: ["Backtests"]
---

# Backtest a strategy

`GET /backtest`

## Request

- Base URL: `https://api.getchord.xyz/v1`
- URL: `https://api.getchord.xyz/v1/backtest`
- Auth: none declared

One bar-close strategy replayed over stored candles of a Hyperliquid perp or an FX pair / metal. Simulated: nothing is traded.

**Model** (the same text comes back in `assumptions`):
- A signal is computed on a CLOSED bar and filled at the NEXT bar's open.
- One position at a time, all-in (equity × `leverage`), long, short or both.
- `feeBps` is charged per side on notional. Default 4.5 for Hyperliquid, 1 for FX.
- `stopPct` / `takePct` are checked inside each bar against its high and low. When both could hit in the same bar the stop is taken first; a bar that opens beyond the level fills at its open.
- A position still open at the last bar is closed at that bar's close (`reason: "end"`). Equity at or below zero ends the run.
- No funding, no spread beyond the fee, no slippage. FX uses Dukascopy BID prices.

**Strategies and their parameters** (pass only the ones you want to change):

| `strategy` | Rule | Parameters (default) |
| --- | --- | --- |
| `supertrend` | Long while the Supertrend direction is up, short while it is down. | `atr` (10), `mult` (3) |
| `ema_cross` | Long while EMA(`fast`) is above EMA(`slow`), short while below. | `fast` (20), `slow` (50) |
| `rsi` | Long when RSI falls below `lower`, short when it rises above `upper`, flat again when RSI crosses back through 50. | `length` (14), `lower` (30), `upper` (70) |
| `donchian` | Long on a close above the highest high of the previous `length` bars, short on a close below their lowest low; held until the opposite breakout. | `length` (20) |
| `buy_hold` | Always long. | none |

**Symbols**: any Hyperliquid perp by its coin id, matched without case (`BTC`, `eth`, `kPEPE`); a leading `$` and a trailing `USDT` / `USD` / `-PERP` are dropped. FX and metals: `EURUSD`, `GBPUSD`, `USDJPY`, `USDCHF`, `AUDUSD`, `USDCAD`, `USDSEK`, `NZDUSD`, `EURJPY`, `GBPJPY`, `EURGBP`, `XAUUSD` (also `gold`), `XAGUSD` (also `silver`).

**How much history there is**: the window is the last `days` ending now, cut to what the store holds. Hyperliquid candles start from what its API served when the store was built (1m a few days, 15m about seven weeks, 1h about seven months, 4h and 1d years) and grow from there; FX has 1h bars since 2016 and 1m bars for about the last 400 days (`/fx/lake` shows how far each symbol is filled). Fewer than 50 bars in the window is a `400` whose message says what the store keeps. Read `from`, `to` and `bars` in the answer for the window that was really tested.

## Query parameters

- `symbol` string
- `interval` '1m' | '5m' | '15m' | '30m' | '1h' | '4h' | '1d'
- `strategy` 'supertrend' | 'ema_cross' | 'rsi' | 'donchian' | 'buy_hold'
- `days` integer
- `side` 'long' | 'short' | 'both'
- `atr` number
- `mult` number
- `fast` number
- `slow` number
- `length` number
- `lower` number
- `upper` number
- `feeBps` number
- `stopPct` number
- `takePct` number
- `leverage` number

## Response `200`

- object
  - `strategy` 'supertrend' | 'ema_cross' | 'rsi' | 'donchian' | 'buy_hold'
  - `params` object: The parameters the run used, defaults filled in.
  - `side` string: long, short or both.
  - `metrics` object
    - `totalReturnPct` number: Return of the strategy over the window, after fees.
    - `buyHoldPct` number: Return of holding the asset over the same bars.
    - `cagrPct` number, nullable: Annualised return. Null when the tested window is shorter than three months, or equity ended at zero.
    - `maxDrawdownPct` number: Largest peak-to-trough fall of equity, as a positive number.
    - `sharpe` number, nullable: Mean over standard deviation of per-bar returns, annualised by the bar size. Null when equity never moved.
    - `trades` integer: Closed trades in the window.
    - `winRatePct` number, nullable: Share of trades that made money. Null with no trades.
    - `profitFactor` number, nullable: Sum of winning returns over sum of losing returns. Null when nothing lost.
    - `avgWinPct` number, nullable: Null with no winning trade.
    - `avgLossPct` number, nullable: Negative. Null with no losing trade.
    - `exposurePct` number: Share of bars spent in a position.
    - `feesPaidPct` number: Sum of the fees charged (each side of each trade, times leverage), in percent.
  - `equity` object[]: The equity curve, thinned to about 300 points.
    - `t` integer: Unix time in MILLISECONDS (every time field of this endpoint is; the rest of the API uses seconds).
    - `equity` number: Strategy equity, starting at 1.
    - `hold` number: Buy-and-hold equity, starting at 1.
  - `trades` object[]: The most recent 200 trades, oldest first.
    - `side` 'long' | 'short'
    - `entryT` integer: Unix time in MILLISECONDS (every time field of this endpoint is; the rest of the API uses seconds).
    - `entry` number: Fill price: the open of the bar after the signal.
    - `exitT` integer: Unix time in MILLISECONDS (every time field of this endpoint is; the rest of the API uses seconds).
    - `exit` number
    - `retPct` number: Return of the trade after both fees, leverage applied.
    - `reason` 'signal' | 'stop' | 'take' | 'end': `end` = still open at the last bar, closed there.
    - `bars` integer: Bars held.
  - `symbol` string: The symbol as resolved: the Hyperliquid coin id or the FX symbol.
  - `source` 'hyperliquid' | 'dukascopy'
  - `interval` '1m' | '5m' | '15m' | '30m' | '1h' | '4h' | '1d'
  - `from` integer: Unix time in MILLISECONDS (every time field of this endpoint is; the rest of the API uses seconds).
  - `to` integer: Unix time in MILLISECONDS (every time field of this endpoint is; the rest of the API uses seconds).
  - `bars` integer: Bars the run used. `from` and `to` are the first and last of them.
  - `assumptions` string: The model of this run in one sentence: fill rule, fee, leverage, price source.

## Other responses

- `400`: Invalid parameters — `error` names the check that failed (`INVALID_PARAMS`, `BAD_ADDRESS`, `BAD_LIMIT`, `BAD_HOURS`, …).
- `503`: The lane behind this endpoint is not ready or not enabled here (`NOT_READY`, `CHAIN_NOT_ENABLED`, `NOT_ENABLED`, `*_UNAVAILABLE`). Retry later; do not treat it as an empty answer.

---

[API](https://skmtc.dev/getchord/apis/chord-api.md) · [All operations](https://skmtc.dev/getchord/apis/chord-api/llms.txt) · [OpenAPI document](https://skmtc.dev/getchord/apis/chord-api/revisions/d3bc4640fea0?raw)
