Hyperliquid candles and OHLCV data: the 5000 candle limit and older history
Hyperliquid serves candles through one info request, candleSnapshot, and keeps only the most recent 5000 per coin and interval. This page covers the request, how far back 5000 candles reach, how to build older candles from fills, a candles REST API, and a pandas backtest.
The short version#
- API:
candleSnapshot, 14 intervals from 1m to 1M. Only the most recent 5000 candles. - Reach: 5000 candles is about 3.5 days at 1m and about 208 days at 1h.
- Archive: the S3 buckets have no candles. They have every fill, and candles can be built from those.
- REST with history: Ironflow
/v1/candlesat every Hyperliquid interval from 1m to 1w, computed from stored fills.
1. candleSnapshot#
Send a coin, an interval and a time range to the info endpoint. Times are Unix milliseconds.
curl -s https://api.hyperliquid.xyz/info \
-H 'Content-Type: application/json' \
-d '{"type":"candleSnapshot","req":{"coin":"BTC","interval":"1h","startTime":1793080800000,"endTime":1793084399999}}'[{"t": 1793080800000, "T": 1793084399999, "s": "BTC", "i": "1h",
"o": "79957.0", "c": "79647.0", "h": "79992.0", "l": "79552.0",
"v": "752.19494", "n": 6594}]| Field | Ironflow | Meaning |
|---|---|---|
| t | timestamp | Open time, Unix milliseconds. |
| T | not returned | Close time, the last millisecond of the interval. |
| o, h, l, c | open, high, low, close | Prices as decimal strings. |
| v | volume | Volume in the base asset. BTC candles count BTC. |
| n | trade_count | Number of trades. Each trade counted once. |
| s, i | market, interval | Coin and interval. |
- Intervals: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 8h, 12h, 1d, 3d, 1w, 1M. Any other value, such as 2m, returns HTTP 422.
- HIP-3 markets use the dex prefix:
"coin":"xyz:NVDA". - Response size: one response can carry the full 5000. Our 40 day 3m request returned 5007 candles in one response.
- Weight: 20, plus more for every 60 candles returned. The rate limits guide has the details.
2. The 5000 candle limit#
The docs say: "Only the most recent 5000 candles are available." The cap counts candles, so how far back you can go depends on the interval:
| Interval | 5000 candles reach back |
|---|---|
| 1m | about 3.5 days |
| 3m | about 10.4 days |
| 5m | about 17 days |
| 15m | about 52 days |
| 1h | about 208 days |
| 4h | about 833 days |
| 1d | 5000 days, about 13.7 years |
Past the cap the API does not return an error. A 1m request for an hour 30 days ago returned [] with HTTP 200. A request that straddles the cap returns only the newer part: we asked for 40 days of 3m candles and the first one was 10.4 days old. Compare the first t with your startTime before you trust a series.
3. Building candles from fills#
Hyperliquid's historical data docs say candles are not provided via S3. Fills are. node_fills_by_block in s3://hl-mainnet-node-data holds every fill, one hourly file per hour, and a node writes the same files going forward. The trades and fills guide covers download and format.
Three rules make the result match Hyperliquid's candles:
- Order by block, then position in the block. Many fills share one timestamp. The last one in the block is the close.
- Keep one row per
tid. Every trade has a fill for each side. Without this, volume doubles. - Volume is the sum of size in the base asset. Trade count is the number of distinct
tid.
import json
import pandas as pd
rows = []
with open("9") as f: # one unpacked hour of node_fills_by_block
for line in f:
block = json.loads(line)
for i, (address, fill) in enumerate(block["events"]):
if fill["coin"] == "BTC":
rows.append({"block": block["block_number"], "i": i, "time": fill["time"],
"tid": fill["tid"], "px": float(fill["px"]), "sz": float(fill["sz"])})
# execution order, then one row per trade (each trade has a fill per side)
df = pd.DataFrame(rows).sort_values(["block", "i"]).drop_duplicates("tid")
df.index = pd.to_datetime(df["time"], unit="ms", utc=True)
candles = df["px"].resample("1min").ohlc()
candles["volume"] = df["sz"].resample("1min").sum()
candles["trades"] = df["tid"].resample("1min").count()
print(candles.dropna())We ran the same logic on our own BTC fills for 08:20 to 08:24 UTC on 27 October 2026. All five 1m candles matched candleSnapshot on open, high, low, close, volume and trade count. resample emits empty rows for minutes with no trades. dropna removes them. For a backtest, forward-fill the close instead.
4. Ironflow /v1/candles#
Ironflow computes candles from the fills it stores and serves them at https://api.ironflow.sh/v1/candles. It answers without a key. market and interval are required.
# three hourly BTC candles from 06:00 UTC on 27 October 2026
curl "https://api.ironflow.sh/v1/candles?market=BTC-PERP&interval=1h&from=1793080800000&limit=3"
# a HIP-3 market
curl "https://api.ironflow.sh/v1/candles?market=xyz:NVDA-PERP&interval=1h&limit=24"Free keyWithout a key the API reaches 24 hours back. A free key reaches 30 days at 60 requests a minute. Get one by email.
The first candle in data from the first call, with the repeated venue fields trimmed:
{
"market": "BTC-PERP",
"interval": "1h",
"timestamp": 1793080800000,
"open": "79957",
"high": "79992",
"low": "79552",
"close": "79647",
"volume": "752.1949400000003",
"trade_count": 6594
}- Intervals: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 8h, 12h, 1d, 3d and 1w, the same set as Hyperliquid's
candleSnapshotexcept 1M. Any other value returns 400INVALID_PARAMETER. - Parameters:
fromandtoin Unix milliseconds,limitup to 1000,cursorfrompagination.next_cursor. - Values are strings. Volume can carry float noise, as in
752.1949400000003. Parse and round. - History: 24 hours without a key, 30 days with a free key, up to 12 months on paid plans. 10 requests per minute without a key, 60 with one.
- Checked against candleSnapshot: on 31 October 2026, 16 hours of 1m candles for BTC and xyz:NVDA matched on open, high, low, close, volume and trade count in all 1915 minutes. Over 24 hours, 6 of 1437 BTC minutes and 13 of 1438 NVDA minutes differed by one tick, each at a liquidation fill.
- Minutes without trades have no candle here.
candleSnapshotreturns them withn0.
Start on an interval boundary#
Without from, the window starts limit intervals back from now. The first candle then covers only part of its interval, and the interval in progress lands on the next page. At 08:21 UTC, limit=2 returned a 06:00 candle with 4547 trades and the 07:00 candle, with has_more true. With from set to 06:00 the 06:00 candle had 6594 trades, equal to candleSnapshot. A from older than your history window is moved up to the start of that window, with the same effect on the first candle. When you page to the end, the last candle is the interval still in progress.
5. pandas backtest example#
This loads hourly ETH candles into a DataFrame and runs a toy moving average rule. Without a key it gets 24 hours. With IRONFLOW_API_KEY set it gets 30 days.
import os
import time
import requests
import pandas as pd
headers = {}
if os.getenv("IRONFLOW_API_KEY"):
headers["Authorization"] = f"Bearer {os.environ['IRONFLOW_API_KEY']}"
# Start on an hour boundary inside your history window so the first candle is complete.
DAYS = 30 if headers else 1
HOUR = 3_600_000
start = (int(time.time() * 1000) - DAYS * 86_400_000) // HOUR * HOUR + HOUR
params = {"market": "ETH-PERP", "interval": "1h", "from": start, "limit": 1000}
rows = []
while True:
page = requests.get("https://api.ironflow.sh/v1/candles", params=params, headers=headers).json()
rows += page["data"]
if not page["pagination"]["has_more"]:
break
params["cursor"] = page["pagination"]["next_cursor"]
df = pd.DataFrame(rows)
df.index = pd.to_datetime(df["timestamp"], unit="ms", utc=True)
df = df[["open", "high", "low", "close", "volume"]].astype(float)
# Toy rule: long when the 6 hour mean is above the 12 hour mean
fast, slow = df["close"].rolling(6).mean(), df["close"].rolling(12).mean()
df["position"] = (fast > slow).astype(int).shift(1).fillna(0)
df["pnl"] = df["close"].pct_change().fillna(0) * df["position"]
print("candles:", len(df), "return:", round((1 + df["pnl"]).prod() - 1, 4))The position uses the previous candle's signal, so the rule never trades on a close it has not seen yet. The last row is the hour in progress. Fees, funding and slippage are left out.
Questions#
What does "only the most recent 5000 candles" mean on Hyperliquid?#
candleSnapshot keeps the newest 5000 candles per coin and interval. At 1m that is about 3.5 days. A request for an older window returns an empty list with HTTP 200 and no error message.
How do I get more than 5000 candles from Hyperliquid?#
Use a larger interval, build candles from the fills in the S3 archive, or use an API that computes candles from stored fills, such as Ironflow's /v1/candles.
Does the Hyperliquid S3 archive have candles?#
No. Hyperliquid's docs say candles are not provided via S3. The archive has every fill, and candles can be built from those.
What candle intervals does Hyperliquid support?#
1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 8h, 12h, 1d, 3d, 1w and 1M. Any other value returns HTTP 422.
Where can I get Hyperliquid OHLCV data for backtesting?#
For recent windows, candleSnapshot. For longer history at small intervals, candles built from fills: from the S3 archive yourself, or from /v1/candles over REST.
Is Hyperliquid candle volume in coins or USD?#
In the base asset. A BTC candle's v is BTC. Multiply by price for a USD estimate.
Related guides#
- Hyperliquid historical data: API, S3 archive and Python
- Hyperliquid trades and fills data
- Hyperliquid API rate limits explained
References#
- Hyperliquid docs: Info endpoint. candleSnapshot, its intervals and the 5000 candle limit.
- Hyperliquid docs: Historical data. The S3 buckets, and the note that candles are not in them.
- Hyperliquid docs: L1 data schemas. Field layout of the fills files candles are built from.
- Hyperliquid docs: Rate limits and user limits. Extra weight per 60 candles returned.
Candles at every Hyperliquid interval from 1m to 1w, computed from every stored fill, are on the API page. A free key raises history to 30 days and the rate to 60 requests per minute.