Backtesting a trading strategy on Hyperliquid historical data with Python

Hyperliquid backtest in four steps

  1. Data. Candles (candleSnapshot, latest 5,000 per interval) and hourly funding (fundingHistory, back to May 12, 2023 for BTC) from the public API with no key, using the requests in our Hyperliquid API endpoint cookbook. Order-book snapshots, asset contexts and every fill from the official S3 archive: Hyperliquid historical data.
  2. Fees. 0.045% taker / 0.015% maker at the base tier; with code HOLYGRAIL, 0.0432% / 0.0144% for your first $25M of volume.
  3. Funding. Every hour a perp is open: size × oracle price × the hourly rate, paid by longs when the rate is positive.
  4. Slippage. Measure it from the live book: on October 19, 2026, a $10,000 BTC market buy cost 0.06 bp vs the mid, the same size in ZRO 6.59 bp.

Our run on October 19, 2026: a BTC 4-hour EMA 20/50 long/short rule at 1x made +16.70% (max drawdown 33.24%, 79 fills) while BTC lost 28.51%. EMA 10/30 on the same data: −48.48%.

Backtesting on Hyperliquid's data because you plan to trade there? Open the account with code HOLYGRAIL and the fee line in your backtest is the one you pay live: 0.0432% taker instead of 0.045% on your first $25M (the code can only be added before $10,000 of volume). Open Hyperliquid with HOLYGRAIL →

1. The four steps in detail

Before any data: write the rule so a computer could follow it. “Buy strength” is not a rule. “Go long at the next 4-hour open when the 20-period EMA of closes is above the 50-period EMA; otherwise short” is. Then test it on the venue you will trade, because prices, funding and fees differ between exchanges.

Step 1: data from the API or the S3 archive

SourceWhat it gives youLimits (docs, October 19, 2026)
candleSnapshot (API)OHLCV candles, 14 intervals from 1m to 1MLatest 5,000 per coin and interval: ≈ 833 days of 4h, ≈ 208 days of 1h; every daily candle
fundingHistory (API)Hourly funding rate and premium per coin500 rows per call; page with the last timestamp + 1; back to May 12, 2023 for BTC
l2Book, metaAndAssetCtxs (API)Current order book; current open interest, mark and fundingSnapshots only: record them over the WebSocket for your own history
Official S3 archiveHourly L2 book snapshots, daily asset contexts (funding, OI, mark, oracle) and every fillRequester pays (AWS account); uploaded about monthly; no candles

The request bodies, sample responses and rate-limit weights for the API rows are in the Hyperliquid API endpoint cookbook. Rate limits matter for bulk pulls. Your IP gets 1,200 weight a minute. Info requests weigh 20, fundingHistory adds 1 per 20 rows and candleSnapshot 1 per 60 candles, so a full 500-row funding page costs about 45 and a year of hourly funding (18 pages) about 810. That is fine once, too much if you run three copies at once: we got HTTP 429 errors doing exactly that, which is why the script below backs off and pauses between pages.

For more than 5,000 candles, build them from the S3 fills or record the WebSocket yourself. Bucket names, file layout, the download commands and the paid providers that sell candles and tick data are in our Hyperliquid historical data guide.

Step 2: fees, with and without a referral code

Charge a fee on every fill, entry and exit. Hyperliquid's base tier applies below $5M of 14-day volume, which is where almost every backtest belongs:

FillFeePer $10,000With HOLYGRAILPer $10,000
Taker (market / IOC)0.045%$4.500.0432%$4.32
Maker (resting limit / ALO)0.015%$1.500.0144%$1.44
Round trip, taker both sides0.09%$9.000.0864%$8.64

The referral discount (4%) covers your first $25M of volume on the master account (sub-accounts and vaults are excluded) and must be set before the account passes $10,000 of volume. Full schedule and tiers: Hyperliquid fees; for your own order mix, the fee calculator.

A small cost model you can drop into any backtest:

Python: costs.py

TAKER, MAKER = 0.00045, 0.00015       # base tier (under $5M of 14-day volume)
REFERRAL = 0.04                       # code discount, first $25M of volume
SLIPPAGE_BP = 1.0                     # your estimate from the book (step 4)

def fill_cost(notional, maker=False, referral=True):
    fee = MAKER if maker else TAKER
    if referral:
        fee *= 1 - REFERRAL
    slip = 0 if maker else SLIPPAGE_BP / 10_000   # a resting maker order pays no spread
    return notional * (fee + slip)

def funding_payment(size, oracle_px, hourly_rate):
    return size * oracle_px * hourly_rate         # > 0: longs pay it, shorts receive it

print(f"$10k taker, with code:    {fill_cost(10_000):.2f}")
print(f"$10k taker, without code: {fill_cost(10_000, referral=False):.2f}")
print(f"$10k maker, with code:    {fill_cost(10_000, maker=True):.2f}")
print(f"1 BTC long, 1 hour at 0.00125%: {funding_payment(1, 82_645, 0.0000125):.2f}")

Output

$10k taker, with code:    5.32
$10k taker, without code: 5.50
$10k maker, with code:    1.44
1 BTC long, 1 hour at 0.00125%: 1.03

Step 3: funding every hour

Hyperliquid perps settle funding every hour. The payment is position size × oracle price × the hourly rate; a positive rate means longs pay shorts. The baseline is 0.00125% an hour paid by longs (the docs put it at about 11.6% a year), so a backtest on spot data, or one that ignores funding, overstates any long-biased strategy. fundingHistory gives the real hourly rates; the script below sums them into 4-hour buckets and charges them with the right sign, using the candle close as an approximation of the oracle price. Background: funding rates explained.

Step 4: slippage from the order book

Candles show traded prices, not what your size would have paid. Measure it: this walks the live ask side of the book for a market buy of a given size and returns the average fill against the mid, in basis points.

Python: slippage.py

import json, urllib.request

def book(coin):
    req = urllib.request.Request("https://api.hyperliquid.xyz/info", headers={"Content-Type": "application/json"},
                                 data=json.dumps({"type": "l2Book", "coin": coin}).encode())
    with urllib.request.urlopen(req, timeout=30) as r:
        return json.load(r)["levels"]                 # [bids, asks], up to 20 levels each

def slippage_bp(coin, usd):
    """Cost of a market BUY of usd notional vs the mid, in basis points (None = deeper than 20 levels)."""
    bids, asks = book(coin)
    mid = (float(bids[0]["px"]) + float(asks[0]["px"])) / 2
    left, qty = usd, 0.0
    for lvl in asks:                                  # walk the ask side level by level
        px, sz = float(lvl["px"]), float(lvl["sz"])
        take = min(left, px * sz)
        qty += take / px
        left -= take
        if left <= 0:
            return (usd / qty / mid - 1) * 10_000     # average fill vs mid
    return None

for coin, usd in [("BTC", 10_000), ("BTC", 1_000_000), ("HYPE", 50_000), ("ZRO", 10_000)]:
    bp = slippage_bp(coin, usd)
    print(f"{coin:5} ${usd:>9,}  {bp:.2f} bp" if bp is not None else f"{coin} ${usd:,}: deeper than 20 levels")

Output on October 19, 2026, 05:55 UTC

BTC   $   10,000  0.06 bp
BTC   $1,000,000  0.62 bp
HYPE  $   50,000  1.12 bp
ZRO   $   10,000  6.59 bp

So 1 bp is a conservative allowance for small BTC orders, but a thin altcoin costs several basis points even at $10,000, and the book changes minute to minute: sample it at different hours before you trust a small-cap backtest. Order types and price impact in detail: Hyperliquid slippage guide.

2. Runnable Python backtest (fees, funding, slippage)

Standard library only (Python 3.8+), no API key. Save as backtest.py and run python3 backtest.py. It downloads a year of BTC 4-hour candles and hourly funding, runs an EMA 20/50 crossover that is always long or short at 1x leverage, and prints the result. Set REFERRAL = True to model a code's 4% fee discount.

Python: backtest.py

import json, time, urllib.error, urllib.request

API = "https://api.hyperliquid.xyz/info"
COIN = "BTC"
DAYS = 365
FAST, SLOW = 20, 50          # EMA lengths on 4h candles
TAKER = 0.00045              # 0.045% per side (base tier)
REFERRAL = False             # True: a referral code's 4% off (first $25M of volume)
SLIPPAGE = 0.0001            # 1 bp per fill: measure it from the book (step 4)
if REFERRAL:
    TAKER *= 1 - 0.04
START_EQUITY = 10_000.0

def post(body):
    req = urllib.request.Request(API, data=json.dumps(body).encode(),
                                 headers={"Content-Type": "application/json"})
    for attempt in range(5):
        try:
            with urllib.request.urlopen(req, timeout=30) as r:
                return json.load(r)
        except urllib.error.HTTPError as e:
            if e.code != 429:
                raise
            time.sleep(2 ** attempt)                     # rate-limited: back off
    raise RuntimeError("rate limited")

end = int(time.time() // 14400 * 14400 * 1000)          # last full 4h boundary (ms)
start = end - DAYS * 86_400_000

candles = post({"type": "candleSnapshot",
                "req": {"coin": COIN, "interval": "4h", "startTime": start, "endTime": end}})
candles = [c for c in candles if c["T"] < end]           # closed candles only

funding, t = [], start
while t < end:                                           # 500 rows per call
    page = post({"type": "fundingHistory", "coin": COIN, "startTime": t, "endTime": end})
    if not page:
        break
    funding += page
    t = page[-1]["time"] + 1
    time.sleep(0.5)                                      # stay under the IP weight limit
rates = {}
for f in funding:                                        # bucket hourly rates into 4h bars
    bar = f["time"] // 14_400_000 * 14_400_000
    rates[bar] = rates.get(bar, 0.0) + float(f["fundingRate"])

def ema(values, n):
    k, out = 2 / (n + 1), []
    for v in values:
        out.append(v if not out else out[-1] + k * (v - out[-1]))
    return out

closes = [float(c["c"]) for c in candles]
opens = [float(c["o"]) for c in candles]
fast, slow = ema(closes, FAST), ema(closes, SLOW)

equity, pos, units = START_EQUITY, 0, 0.0                # pos: +1 long, -1 short
fees = fund = 0.0
trades = 0
peak, max_dd = equity, 0.0
for i in range(SLOW, len(candles)):
    # 1) signal from the PREVIOUS closed bar, executed at this bar's open (no look-ahead)
    want = 1 if fast[i - 1] > slow[i - 1] else -1
    px = opens[i]
    if pos != 0:
        equity += pos * units * (px - float(candles[i - 1]["c"]))   # mark to open
    if want != pos:
        notional = equity                                 # 1x leverage
        if pos != 0:                                      # close old position
            cost = units * px * (TAKER + SLIPPAGE)
            equity -= cost; fees += cost; trades += 1
        units = notional / px
        cost = units * px * (TAKER + SLIPPAGE)
        equity -= cost; fees += cost; trades += 1
        pos = want
    # 2) hold through the bar: price move open -> close, funding for the bar's hours
    close = closes[i]
    equity += pos * units * (close - px)
    f = pos * units * close * rates.get(candles[i]["t"], 0.0)   # longs pay positive funding
    equity -= f; fund += f
    peak = max(peak, equity); max_dd = max(max_dd, 1 - equity / peak)

first, last = candles[SLOW]["t"], candles[-1]["T"]
fmt = lambda ms: time.strftime("%Y-%m-%d %H:%M", time.gmtime(ms / 1000))
print(f"{COIN} 4h EMA{FAST}/{SLOW} long/short, {fmt(first)} -> {fmt(last + 1)} UTC")
print(f"candles {len(candles)}, funding rows {len(funding)}, fills {trades}")
print(f"final equity {equity:,.2f}  return {equity / START_EQUITY - 1:+.2%}  max drawdown {max_dd:.2%}")
print(f"fees+slippage {fees:,.2f}  funding paid {fund:,.2f}")
print(f"buy and hold {closes[-1] / opens[SLOW] - 1:+.2%}")

Output on October 19, 2026, 05:53 UTC

BTC 4h EMA20/50 long/short, 2025-10-27 12:00 -> 2026-10-19 04:00 UTC
candles 2190, funding rows 8760, fills 79
final equity 11,669.99  return +16.70%  max drawdown 33.24%
fees+slippage 409.26  funding paid 77.17
buy and hold -28.51%

How it avoids the usual traps:

  • No look-ahead. The signal uses EMAs up to the previous closed candle and fills at the current candle’s open. The unfinished candle is dropped.
  • Costs on every fill. Each flip is two fills (close and reopen), each paying the taker fee plus 1 bp slippage.
  • Funding with the right sign. Hourly rates are summed into 4-hour buckets; positive rates are paid by longs and received by shorts.
  • Mark-to-market drawdown. Equity is updated at every open and close, so the drawdown includes open losses.

3. Our result on October 19, 2026

We ran the script at 05:53 UTC on October 19, 2026. It downloaded 2,190 four-hour candles (October 19, 2025 04:00 to October 19, 2026 04:00 UTC) and 8,760 hourly funding rates. The first trade came after the 50-candle warm-up, on October 27, 2025.

MetricEMA 20/50 long/short, 1x
Final equity (from $10,000)$11,669.99
Return+16.70%
Maximum drawdown33.24%
Fills79
Fees + slippage paid$409.26
Net funding paid$77.17
BTC buy-and-hold, same window−28.51%

Your run will differ: the window rolls forward every four hours. Our first published run (September 25, 2026) returned +15.77% with a 33.15% drawdown against −31.48% for buy-and-hold. Past results do not predict future ones.

On its face, a trend rule that made money while BTC fell by more than a quarter. Costs took a real bite: without fees, slippage and funding the same trades returned +22.75%, so costs removed about six percentage points, and $409 of the $486 in costs was trading fees and slippage. A 33% drawdown on a 1x strategy would also be hard to sit through in real life.

4. Is it robust? Stress tests

We re-ran the same downloaded data with one change at a time:

Variant (October 19, 2026)ReturnMax DDWhat it tells you
Base: EMA 20/50, taker + 1 bp+16.70%33.24%—
Taker with a referral code (0.0432%)+16.86%33.21%$13 less in fees on $10,000 over 79 fills; the saving scales with size and turnover
No fees, slippage or funding+22.75%32.56%Costs ≈ 6 points a year at 79 fills
Maker fee 0.015%, no slippage+20.42%32.68%Limit orders help, if they fill
Slippage 5 bp instead of 1 bp+13.09%33.82%Thin markets eat the edge
EMA 10/30 (faster)−48.48%55.16%203 fills; the edge is parameter-dependent
EMA 50/200 (slower, from Nov 21, 2025)+5.05%21.07%Calmer, but BTC was flat (+0.22%) over its shorter window
Long-only (flat instead of short)−5.35%24.62%Paid $259 funding, more than its $181 of fees; the profit came from shorts
First half only (to Apr 19, 2026)−0.27%33.24%Flat while BTC fell 34.29%
Second half only+16.95%13.45%BTC +8.81% in the same half

This table is the honest summary. The answer flips from +17% to −48% depending on two numbers, the long side alone lost money, and the two halves of the year look nothing alike. If we had tried ten EMA pairs and reported the best one, the article would be advertising noise. Robust edges survive neighbouring parameters, both halves of the data and higher costs; this one does not clearly survive any of them. The referral code changes the fee line, not the verdict: it matters for strategies that trade a lot, not for rescuing a weak rule.

5. Backtesting pitfalls

Look-ahead bias

Using information that was not available when the decision was made: acting at a candle’s close on a signal computed from that same close, using the current (still-forming) candle, or using indicators recalculated with later data. The fix is structural: decide on closed bars, fill at the next bar.

Survivorship bias

Testing only coins that still trade today. Hyperliquid's meta listed 234 perp markets on October 19, 2026, of which 56 were delisted; a “top-50 altcoin” basket chosen from today's list quietly excludes the ones that collapsed. Include delisted markets where you can, or at least say you did not.

Fees

A strategy that flips often lives or dies by fees. At the base taker rate a round trip costs 0.09% of notional before slippage; the EMA 10/30 variant paid $764 in costs on $10,000. Model the fee you will actually pay (step 2), including any referral discount.

Over-fitting and regime dependence

Every parameter you tune on the data you report inflates the result. Split the history: tune on the first part, then run once on the rest and report that. Test at least one rising and one falling period, as in the table above.

Leverage and liquidation

The script trades at 1x. At higher leverage, a 33% drawdown is a liquidation long before it is a drawdown. Model the maintenance margin (1.25% for BTC at 40x on Hyperliquid) if you test leverage, and check levels with the liquidation price calculator.

6. No-code route: TradingView Strategy Tester

  1. Open the chart HYPERLIQUID:BTCUSDC.P on tradingview.com; price and volume come from Hyperliquid’s own book.
  2. Add a strategy: a built-in one from Indicators, or your own Pine Script with strategy().
  3. In the strategy’s Properties, set commission as a percentage (0.045% for Hyperliquid taker, 0.0432% with a referral code) and slippage in ticks. In Pine these are the commission_type, commission_value and slippage parameters.
  4. Read the report in the Strategy Tester tab in the bottom panel: Metrics for the summary, Trades for every trade.
Hyperliquid BTCUSDC perpetual chart on TradingView
HYPERLIQUID:BTCUSDC.P on tradingview.com (September 2026): the chart the Strategy Tester runs on uses Hyperliquid’s own prices and volume.

Know the emulator’s assumptions. TradingView’s docs say that on historical bars the broker emulator fills orders after a bar closes using only chart data, and assumes the price went open → high → low → close when the open is nearer the high (the reverse otherwise). Finer intrabar fills (use_bar_magnifier) need a Premium or Ultimate plan. There is no funding setting, so for positions held more than a day add funding yourself. Setup details: Hyperliquid on TradingView and custom indicators.

7. From backtest to live trading

  1. Paper-trade or use the testnet for a few weeks with the exact rules (Hyperliquid testnet guide).
  2. Start small and compare live fills and funding with the backtest; the gap is your real slippage.
  3. Automate carefully with an API wallet that cannot withdraw: setup in the Hyperliquid API quickstart, key handling in the API key security guide, strategy patterns in Hyperliquid automated trading strategies.
  4. Keep a journal and stop when live results break the backtest’s worst drawdown.

8. FAQ

How do I run a Hyperliquid backtest?

Four steps. (1) Get the data: candles from candleSnapshot and hourly funding from fundingHistory on Hyperliquid's public API (no key), or deeper history such as hourly order-book snapshots, daily asset contexts and every fill from the official S3 archive. (2) Charge the current fees on every fill: 0.045% taker and 0.015% maker at the base tier, or 0.0432% / 0.0144% with a referral code for the first $25M of volume. (3) Apply funding for every hour a position is open. (4) Add slippage measured from the order book. The Python script on this page does all four in about 90 lines.

Where do I get Hyperliquid historical data for backtesting?

From the public info endpoint for free: candleSnapshot returns the latest 5,000 candles per coin and interval (about 833 days of 4-hour candles, 208 days of 1-hour candles, and every daily candle), and fundingHistory pages back to May 12, 2023 for BTC. Order books, open interest and public trades are current-only in the API; their history lives in Hyperliquid's official S3 archive (requester pays): hourly L2 snapshots, daily asset contexts and every fill, with the bucket paths in our Hyperliquid historical data guide. S3 has no candles.

How do I include Hyperliquid fees in a backtest?

Charge a fee on every fill, both the entry and the exit: 0.045% for taker fills (market orders, IOC) and 0.015% for maker fills (resting limit or ALO) at the base tier, below $5M of 14-day volume. With a referral code such as HOLYGRAIL both drop 4% for the first $25M of volume, to 0.0432% and 0.0144%. A round trip at taker costs 0.09% of notional before slippage.

What result did the example strategy get?

Run at 05:53 UTC on October 19, 2026, a BTC 4-hour EMA 20/50 crossover, always long or short at 1x, returned +16.70% over the previous year after 0.045% taker fees, 1 bp slippage and funding, with a 33.24% maximum drawdown, while buying and holding BTC lost 28.51%. Change the EMAs to 10/30 and the same test loses 48.48%, which is the real lesson: one good parameter set proves little.

Why include funding in a perpetual futures backtest?

Because a position held for weeks pays or receives funding every hour on Hyperliquid, and it is usually paid by longs. In our October 19, 2026 test a long-only version of the strategy paid $259 of funding on a $10,000 account in a year, more than its $181 of fees and slippage, and lost 5.35%.

Can I backtest without coding?

Use TradingView's Strategy Tester: load the HYPERLIQUID:BTCUSDC.P chart, add a built-in strategy or a Pine Script strategy, and set commission (0.045% for Hyperliquid taker) and slippage in the strategy's Properties. It shows metrics and a trade list in the bottom panel. It does not model hourly funding, so add that cost by hand for positions held longer than a day.

What are the most common backtesting mistakes?

Look-ahead bias (acting on a bar’s close at that same close), survivorship bias (testing only coins that still trade), ignoring fees, slippage and funding, over-optimising parameters on the same data you report, and testing one market regime only. Hold out data you never tune on, and paper-trade before going live.

Tested it? Trade it with lower fees

Hyperliquid with HOLYGRAIL: 4% off trading fees on your first $25M of volume (0.0432% taker instead of 0.045%), on the same API your backtest used.

Start on Hyperliquid →

We earn a commission when you sign up through our links; it never raises your fees.

Disclosure: HOLYGRAIL is our Hyperliquid referral code; we earn a share of referred accounts’ fees at no cost to you. The backtest was run by us on October 19, 2026 against Hyperliquid’s public API; it is an educational example, not a trading recommendation. Past performance does not predict future results.