> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ironflow.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# NautilusTrader Hyperliquid backtesting data

> Load Hyperliquid history into a NautilusTrader catalog past the 5,000-candle limit: free hourly bars since June 2025 with no key, 1m bars with a key.

Hyperliquid's API only returns about the last 5,000 candles per interval, so a NautilusTrader backtest on Hyperliquid cannot reach far. Load Ironflow history into a `ParquetDataCatalog` instead.

Tested with NautilusTrader 1.230.

## Hourly bars, no key

```python load_catalog.py theme={null}
from decimal import Decimal
from pathlib import Path

import pandas as pd
from nautilus_trader.model.currencies import BTC, USDC
from nautilus_trader.model.data import Bar, BarType
from nautilus_trader.model.identifiers import InstrumentId, Symbol
from nautilus_trader.model.instruments import CryptoPerpetual
from nautilus_trader.model.objects import Price, Quantity
from nautilus_trader.persistence.catalog import ParquetDataCatalog

MONTHS = pd.period_range("2025-06", "2026-10", freq="M").strftime("%Y-%m")
URL = "https://huggingface.co/datasets/ironflow/hyperliquid-candles-1h/resolve/main/data/{}.parquet"
COLS = ["timestamp", "coin", "open", "high", "low", "close", "volume"]

raw = pd.concat(pd.read_parquet(URL.format(m), columns=COLS) for m in MONTHS)
btc = raw[raw.coin == "BTC"].sort_values("timestamp").set_index("timestamp")

# BTC settings. For another coin use szDecimals from Hyperliquid's meta as size_precision.
instrument = CryptoPerpetual(
    instrument_id=InstrumentId.from_str("BTC-USD-PERP.HYPERLIQUID"), raw_symbol=Symbol("BTC"),
    base_currency=BTC, quote_currency=USDC, settlement_currency=USDC, is_inverse=False,
    price_precision=1, size_precision=5, price_increment=Price.from_str("0.1"),
    size_increment=Quantity.from_str("0.00001"), ts_event=0, ts_init=0,
    maker_fee=Decimal("0.00015"), taker_fee=Decimal("0.00045"),
    margin_init=Decimal("0.05"), margin_maint=Decimal("0.025"),
)
bar_type = BarType.from_str("BTC-USD-PERP.HYPERLIQUID-1-HOUR-LAST-EXTERNAL")

# Nautilus stamps a bar at its close, Ironflow candles at their open.
bars = [
    Bar(
        bar_type,
        instrument.make_price(row.open),
        instrument.make_price(row.high),
        instrument.make_price(row.low),
        instrument.make_price(row.close),
        instrument.make_qty(row.volume),
        (int(row.Index.timestamp()) + 3600) * 1_000_000_000,
        (int(row.Index.timestamp()) + 3600) * 1_000_000_000,
    )
    for row in btc.itertuples()
]

Path("catalog").mkdir(exist_ok=True)
catalog = ParquetDataCatalog("catalog")
catalog.write_data([instrument])
catalog.write_data(bars)
print(len(bars), "bars")
```

```bash theme={null}
pip install nautilus_trader pandas pyarrow
python load_catalog.py
```

That is 12,432 hourly bars for BTC. Backtest them with `catalog.bars([str(bar_type)])` and `catalog.instruments()`: add both to a `BacktestEngine` with a `Venue("HYPERLIQUID")` margin account, and run your strategy. Extend `MONTHS` as new months are published.

Set `maker_fee` and `taker_fee` to your own Hyperliquid fee tier.

## 1m bars, with a key

The dataset is hourly. For intraday bars page `/v1/candles` with your key, and build bars from the rows:

```python theme={null}
import os
import time

import pandas as pd
import requests

API = "https://api.ironflow.sh/v1/candles"
HEADERS = {"Authorization": f"Bearer {os.environ['IRONFLOW_KEY']}"}
INTERVAL, STEP_NS = "1m", 60_000_000_000
end = pd.Timestamp.now("UTC").floor("min")
start = end - pd.Timedelta(days=13)  # free key: 14 days, Explorer: 90, Builder: 365

rows = []
params = {"market": "BTC-PERP", "interval": INTERVAL, "limit": 1000,
          "from": int(start.timestamp() * 1000), "to": int(end.timestamp() * 1000)}
while True:
    r = requests.get(API, params=params, headers=HEADERS, timeout=30)
    if r.status_code == 429:
        time.sleep(int(r.headers.get("Retry-After", 5)))
        continue
    r.raise_for_status()
    body = r.json()
    rows += body["data"]
    if not body["pagination"]["has_more"]:
        break
    params["cursor"] = body["pagination"]["next_cursor"]

bar_type = BarType.from_str("BTC-USD-PERP.HYPERLIQUID-1-MINUTE-LAST-EXTERNAL")
bars = [
    Bar(
        bar_type,
        instrument.make_price(float(c["open"])),
        instrument.make_price(float(c["high"])),
        instrument.make_price(float(c["low"])),
        instrument.make_price(float(c["close"])),
        instrument.make_qty(float(c["volume"])),
        c["timestamp"] * 1_000_000 + STEP_NS,
        c["timestamp"] * 1_000_000 + STEP_NS,
    )
    for c in rows
]
catalog.write_data(bars)
```

Run it after the `instrument` and `catalog` definitions above, with `IRONFLOW_KEY=if_your_key`. Needs `requests`.

## Plan limits

| Access | History | Requests |
| - | - | - |
| [Free dataset](https://docs.ironflow.sh/guides/datasets.md), no key | Hourly candles from 25 May 2025, updated monthly | No limit |
| Free key | 14 days | 60 a minute |
| Explorer | 90 days | 100 a minute |
| Builder | 1 year | 1,000 a minute |
| Enterprise | Everything stored | 10,000 a minute |

A free key's 14 days is 20,160 `1m` bars. Hourly it is 336, so use the free dataset for long hourly tests. [Get a free key](https://ironflow.sh/key) or see [all plans](https://docs.ironflow.sh/concepts/rate-limits.md).

Candles are built from every fill, so high, low and trade count can differ from Hyperliquid's own candles in heavy liquidation hours.

Next: [paper trade on Hyperliquid](https://ironflow.sh/guides/hyperliquid-paper-trading) with the official SDK, \$10,000 and live prices.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.