Backtest a strategy
const url = 'https://api.getchord.xyz/v1/backtest?symbol=BTC&interval=1m&strategy=supertrend&days=365&side=long&atr=10&mult=3&fast=20&slow=50&lower=30&upper=70&leverage=1';const options = {method: 'GET'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url 'https://api.getchord.xyz/v1/backtest?symbol=BTC&interval=1m&strategy=supertrend&days=365&side=long&atr=10&mult=3&fast=20&slow=50&lower=30&upper=70&leverage=1'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. feeBpsis charged per side on notional. Default 4.5 for Hyperliquid, 1 for FX.stopPct/takePctare 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.
Parameters
Section titled “ Parameters ”Query Parameters
Section titled “Query Parameters”A Hyperliquid perp coin id or an FX / metal symbol (list above).
Bar size. 5m and 30m are resampled from finer stored bars.
Length of the window, ending now.
Which signals are traded.
supertrend: ATR length.
supertrend: band width in ATRs.
ema_cross: fast EMA length.
ema_cross: slow EMA length.
rsi: RSI length (default 14). donchian: channel length in bars (default 20).
rsi: go long below this.
rsi: go short above this.
Fee per side in basis points of notional. Default 4.5 (Hyperliquid) or 1 (FX).
Stop-loss, percent from entry. Omitted = none.
Take-profit, percent from entry. Omitted = none.
Position size as a multiple of equity. Values outside 0.1–20 are clamped.
Responses
Section titled “ Responses ”object
The parameters the run used, defaults filled in.
object
Long, short or both.
object
Return of the strategy over the window, after fees.
Return of holding the asset over the same bars.
Annualised return. Null when the tested window is shorter than three months, or equity ended at zero.
Largest peak-to-trough fall of equity, as a positive number.
Mean over standard deviation of per-bar returns, annualised by the bar size. Null when equity never moved.
Closed trades in the window.
Share of trades that made money. Null with no trades.
Sum of winning returns over sum of losing returns. Null when nothing lost.
Null with no winning trade.
Negative. Null with no losing trade.
Share of bars spent in a position.
Sum of the fees charged (each side of each trade, times leverage), in percent.
The equity curve, thinned to about 300 points.
object
Unix time in MILLISECONDS (every time field of this endpoint is; the rest of the API uses seconds).
Strategy equity, starting at 1.
Buy-and-hold equity, starting at 1.
The most recent 200 trades, oldest first.
object
Unix time in MILLISECONDS (every time field of this endpoint is; the rest of the API uses seconds).
Fill price: the open of the bar after the signal.
Unix time in MILLISECONDS (every time field of this endpoint is; the rest of the API uses seconds).
Return of the trade after both fees, leverage applied.
end = still open at the last bar, closed there.
Bars held.
The symbol as resolved: the Hyperliquid coin id or the FX symbol.
Unix time in MILLISECONDS (every time field of this endpoint is; the rest of the API uses seconds).
Unix time in MILLISECONDS (every time field of this endpoint is; the rest of the API uses seconds).
Bars the run used. from and to are the first and last of them.
The model of this run in one sentence: fill rule, fee, leverage, price source.
Example
{ "strategy": "supertrend", "params": { "atr": 10, "mult": 3 }, "side": "both", "metrics": { "totalReturnPct": -29.7396335093976, "buyHoldPct": -24.840856481481477, "cagrPct": -29.77364346765159, "maxDrawdownPct": 43.938798730789316, "sharpe": -0.67928989324471, "trades": 58, "winRatePct": 37.93103448275862, "profitFactor": 0.7686488016790433, "avgWinPct": 4.190301346373836, "avgLossPct": -3.3314820839884187, "exposurePct": 99.58847736625515, "feesPaidPct": 5.2199999999999935 }, "equity": [ { "t": 1760227200000, "equity": 1, "hold": 1.000244140625 }, { "t": 1760342400000, "equity": 1, "hold": 1.0321994357638888 } ], "trades": [ { "side": "long", "entryT": 1760356800000, "entry": 114153, "exitT": 1760601600000, "exit": 110526, "retPct": -3.267314656645025, "reason": "signal", "bars": 17 }, { "side": "short", "entryT": 1760601600000, "entry": 110526, "exitT": 1760947200000, "exit": 111160, "retPct": -0.6636206865353033, "reason": "signal", "bars": 24 } ], "symbol": "BTC", "source": "hyperliquid", "interval": "4h", "from": 1760212800000, "to": 1791705600000, "bars": 2188, "assumptions": "Signal on bar close, fill at next open; 4.5 bps fee per side; Hyperliquid perp candles, no funding; no slippage."}Invalid parameters — error names the check that failed (INVALID_PARAMS, BAD_ADDRESS, BAD_LIMIT, BAD_HOURS, …).
Every non-2xx body.
object
Stable machine code, e.g. INVALID_PARAMS, NOT_READY, RATE_LIMIT_EXCEEDED.
For humans.
HTTP status, when included.
Example
{ "error": "INVALID_PARAMS", "message": "amount must be an integer string in the token's smallest unit"}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.
Every non-2xx body.
object
Stable machine code, e.g. INVALID_PARAMS, NOT_READY, RATE_LIMIT_EXCEEDED.
For humans.
HTTP status, when included.
Example
{ "error": "INVALID_PARAMS", "message": "amount must be an integer string in the token's smallest unit"}