Skip to content

Backtest a strategy

GET
/backtest
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.
  • 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.

symbol
string
default: BTC

A Hyperliquid perp coin id or an FX / metal symbol (list above).

interval
string
default: 1h
Allowed values: 1m 5m 15m 30m 1h 4h 1d

Bar size. 5m and 30m are resampled from finer stored bars.

strategy
string
default: supertrend
Allowed values: supertrend ema_cross rsi donchian buy_hold
days
integer
default: 365 >= 1 <= 3650

Length of the window, ending now.

side
string
default: both
Allowed values: long short both

Which signals are traded.

atr
number
default: 10

supertrend: ATR length.

mult
number
default: 3

supertrend: band width in ATRs.

fast
number
default: 20

ema_cross: fast EMA length.

slow
number
default: 50

ema_cross: slow EMA length.

length
number

rsi: RSI length (default 14). donchian: channel length in bars (default 20).

lower
number
default: 30

rsi: go long below this.

upper
number
default: 70

rsi: go short above this.

feeBps
number

Fee per side in basis points of notional. Default 4.5 (Hyperliquid) or 1 (FX).

stopPct
number
> 0

Stop-loss, percent from entry. Omitted = none.

takePct
number
> 0

Take-profit, percent from entry. Omitted = none.

leverage
number
default: 1 >= 0.1 <= 20

Position size as a multiple of equity. Values outside 0.1–20 are clamped.

Media typeapplication/json
object
strategy
string
default: supertrend
Allowed values: supertrend ema_cross rsi donchian buy_hold
params

The parameters the run used, defaults filled in.

object
key
additional properties
number
side

Long, short or both.

string
metrics
object
totalReturnPct

Return of the strategy over the window, after fees.

number
buyHoldPct

Return of holding the asset over the same bars.

number
cagrPct

Annualised return. Null when the tested window is shorter than three months, or equity ended at zero.

number | null
maxDrawdownPct

Largest peak-to-trough fall of equity, as a positive number.

number
sharpe

Mean over standard deviation of per-bar returns, annualised by the bar size. Null when equity never moved.

number | null
trades

Closed trades in the window.

integer
winRatePct

Share of trades that made money. Null with no trades.

number | null
profitFactor

Sum of winning returns over sum of losing returns. Null when nothing lost.

number | null
avgWinPct

Null with no winning trade.

number | null
avgLossPct

Negative. Null with no losing trade.

number | null
exposurePct

Share of bars spent in a position.

number
feesPaidPct

Sum of the fees charged (each side of each trade, times leverage), in percent.

number
equity

The equity curve, thinned to about 300 points.

Array<object>
object
t

Unix time in MILLISECONDS (every time field of this endpoint is; the rest of the API uses seconds).

integer
equity

Strategy equity, starting at 1.

number
hold

Buy-and-hold equity, starting at 1.

number
trades

The most recent 200 trades, oldest first.

Array<object>
object
side
string
Allowed values: long short
entryT

Unix time in MILLISECONDS (every time field of this endpoint is; the rest of the API uses seconds).

integer
entry

Fill price: the open of the bar after the signal.

number
exitT

Unix time in MILLISECONDS (every time field of this endpoint is; the rest of the API uses seconds).

integer
exit
number
retPct

Return of the trade after both fees, leverage applied.

number
reason

end = still open at the last bar, closed there.

string
Allowed values: signal stop take end
bars

Bars held.

integer
symbol

The symbol as resolved: the Hyperliquid coin id or the FX symbol.

string
source
string
Allowed values: hyperliquid dukascopy
interval
string
default: 1h
Allowed values: 1m 5m 15m 30m 1h 4h 1d
from

Unix time in MILLISECONDS (every time field of this endpoint is; the rest of the API uses seconds).

integer
to

Unix time in MILLISECONDS (every time field of this endpoint is; the rest of the API uses seconds).

integer
bars

Bars the run used. from and to are the first and last of them.

integer
assumptions

The model of this run in one sentence: fill rule, fee, leverage, price source.

string
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, …).

Media typeapplication/json

Every non-2xx body.

object
error
required

Stable machine code, e.g. INVALID_PARAMS, NOT_READY, RATE_LIMIT_EXCEEDED.

string
message

For humans.

string
statusCode

HTTP status, when included.

integer
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.

Media typeapplication/json

Every non-2xx body.

object
error
required

Stable machine code, e.g. INVALID_PARAMS, NOT_READY, RATE_LIMIT_EXCEEDED.

string
message

For humans.

string
statusCode

HTTP status, when included.

integer
Example
{
"error": "INVALID_PARAMS",
"message": "amount must be an integer string in the token's smallest unit"
}