
Export Hyperliquid OHLCV Candles to Parquet
Generate Parquet files for one Hyperliquid market, or every discovered market for one interval, from Dwellir's OHLCV endpoint.
Prefer a ready-made file?
If you just want the full archive as one Parquet file without building a fetcher, use the managed Hyperliquid OHLCV Full-History Exports — download a Parquet file for any market and interval directly from the Dwellir dashboard. This guide is for teams that want to control the fetch loop, market selection, and storage layout themselves.
This guide builds a Parquet exporter for Dwellir's Hyperliquid OHLCV API. It supports two paths:
- export one market and one interval, such as
BTC1m - discover all currently listed Hyperliquid markets and export one interval for each market into one Parquet file
The exporter uses the paginated /v1/candles/range endpoint and writes each returned page into a streaming ParquetWriter. That keeps memory bounded when an all-markets export returns millions of sparse candle objects.
Copy-Paste Prompt for Your Coding Tool
Paste this into Claude Code, Codex, Cursor, Windsurf, or another coding agent:
Build me a Python script that exports Hyperliquid OHLCV candles from Dwellir into Parquet.
Requirements:
- First check whether the `dwellir` CLI is installed.
- If it is not installed, recommend installing it with one of these options:
- `brew tap dwellir-public/homebrew-tap && brew install dwellir`
- `curl -fsSL https://raw.githubusercontent.com/dwellir-public/cli/main/scripts/install.sh | sh`
- Ask me to authenticate with `dwellir auth login`.
- Check authentication with `dwellir auth status`.
- Get an enabled API key name with `dwellir keys list --toon`.
- Use `dwellir endpoints search hyperliquid --ecosystem hyperliquid --network mainnet --key <name> --json` to discover both:
- `Hyperliquid Index` for OHLCV range requests
- `Hyperliquid HyperCore Info` for market discovery
- Support `--market BTC` for one market.
- Support `--all-markets` for all markets discovered from Info endpoint calls:
- `{"type":"meta"}` for currently listed native perp names
- `{"type":"spotMeta"}` for spot symbols, using canonical names such as `PURR/USDC` and `@INDEX` names for non-canonical pairs
- `{"type":"perpDexs"}` for HIP-3 `prefix:ASSET` symbols
- Support `--max-markets` for short smoke tests before a full all-markets run.
- Use `GET /v1/candles/range` with `limit=5000`, follow `nextCursor`, and keep `end` exclusive.
- Support `1s`, `1m`, and `5m`.
- Stream pages into Parquet instead of collecting all rows in memory.
- Use columns: s, i, t, T, o, h, l, c, v, q, n, x.
- Show me how to run a one-market export and an all-markets export for one interval.What You Will Learn
- discover the Dwellir Hyperliquid Index and Info endpoint URLs with the CLI
- export one market to Parquet with cursor pagination
- discover native perp, spot, and HIP-3 market symbols from the Info endpoint
- iterate all discovered markets for one interval and stream them into Parquet
- inspect the resulting Parquet file with DuckDB or PyArrow
Prerequisites
Install Python 3.10+, requests, and pyarrow:
python --version
pip install requests pyarrowInstall and authenticate the Dwellir CLI:
brew tap dwellir-public/homebrew-tap
brew install dwellir
dwellir auth login
dwellir auth status
dwellir keys list --toonPick an enabled key name from dwellir keys list --toon. The examples below use YOUR_KEY_NAME.
How the Export Works
The OHLCV range endpoint returns pages of candle objects:
GET https://api-hyperliquid-index.n.dwellir.com/YOUR_API_KEY/v1/candles/rangeEach request includes:
market, for exampleBTC,@142, orhyna:ETHinterval, one of1s,1m, or5mstart, the inclusive lower boundend, the exclusive upper boundlimit=5000cursor, only after the first page whenhasMoreistrue
For all-market exports, we first discover symbols from Dwellir's HyperCore Info endpoint:
| Market type | Info request | Symbol format |
|---|---|---|
| Native perps | {"type":"meta"} | listed name, for example BTC |
| Spot | {"type":"spotMeta"} | canonical name, for example PURR/USDC, otherwise @INDEX, for example @142 |
| HIP-3 perpDexs | {"type":"perpDexs"} | first element of assetToStreamingOiCap, for example hyna:ETH |
Important: the Info endpoint discovers currently listed markets. Historical OHLCV can include markets that are no longer listed; use the dashboard export catalog when you need Dwellir's full indexed catalog including retired symbols.
Python Export Script
Save this as export_ohlcv_parquet.py:
import argparse
import json
import subprocess
import sys
from typing import Any
import pyarrow as pa
import pyarrow.parquet as pq
import requests
VALID_INTERVALS = {"1s", "1m", "5m"}
SCHEMA = pa.schema(
[
("s", pa.string()),
("i", pa.string()),
("t", pa.int64()),
("T", pa.int64()),
("o", pa.string()),
("h", pa.string()),
("l", pa.string()),
("c", pa.string()),
("v", pa.string()),
("q", pa.string()),
("n", pa.int64()),
("x", pa.bool_()),
]
)
def load_endpoint_catalog(key_name: str) -> dict[str, Any]:
try:
raw = subprocess.check_output(
[
"dwellir",
"endpoints",
"search",
"hyperliquid",
"--ecosystem",
"hyperliquid",
"--network",
"mainnet",
"--key",
key_name,
"--json",
],
text=True,
)
except FileNotFoundError as exc:
raise SystemExit("Install the dwellir CLI first, then run `dwellir auth login`.") from exc
return json.loads(raw)
def endpoint_url(catalog: dict[str, Any], endpoint_name: str) -> str:
for item in catalog.get("data", []):
if item.get("name") == endpoint_name:
return item["networks"][0]["nodes"][0]["https"]
raise RuntimeError(f"Could not find endpoint named {endpoint_name!r}")
def post_info(session: requests.Session, info_url: str, payload: dict[str, Any]) -> Any:
response = session.post(info_url, json=payload, timeout=30)
response.raise_for_status()
return response.json()
def discover_markets(session: requests.Session, info_url: str) -> list[str]:
meta = post_info(session, info_url, {"type": "meta"})
spot_meta = post_info(session, info_url, {"type": "spotMeta"})
perp_dexs = post_info(session, info_url, {"type": "perpDexs"})
perps = [
asset["name"]
for asset in meta.get("universe", [])
if isinstance(asset, dict) and asset.get("name") and not asset.get("isDelisted")
]
spots = [
pair["name"] if pair.get("isCanonical") else f"@{pair['index']}"
for pair in spot_meta.get("universe", [])
if isinstance(pair, dict) and pair.get("name") and "index" in pair
]
hip3 = []
dex_items = perp_dexs if isinstance(perp_dexs, list) else perp_dexs.get("dexs", [])
for dex in dex_items:
if not isinstance(dex, dict):
continue
for pair in dex.get("assetToStreamingOiCap", []) or []:
if isinstance(pair, list) and pair:
hip3.append(pair[0])
markets = []
seen = set()
for market in perps + spots + hip3:
if market not in seen:
seen.add(market)
markets.append(market)
return markets
def candle_pages(
session: requests.Session,
index_base_url: str,
market: str,
interval: str,
start: str | None,
end: str | None,
):
cursor = None
while True:
params = {"market": market, "interval": interval, "limit": "5000"}
if cursor:
params["cursor"] = cursor
elif start:
params["start"] = start
if end:
params["end"] = end
response = session.get(
f"{index_base_url}/v1/candles/range",
params=params,
timeout=60,
)
response.raise_for_status()
page = response.json()
rows = page.get("data", [])
if rows:
yield rows
if not page.get("hasMore"):
break
cursor = page["nextCursor"]
def table_from_rows(rows: list[dict[str, Any]]) -> pa.Table:
return pa.Table.from_pylist(rows, schema=SCHEMA)
def export_parquet(args: argparse.Namespace) -> int:
if args.interval not in VALID_INTERVALS:
raise SystemExit(f"Unsupported interval: {args.interval}")
if bool(args.market) == bool(args.all_markets):
raise SystemExit("Pass exactly one of --market or --all-markets")
catalog = load_endpoint_catalog(args.key_name)
index_base_url = endpoint_url(catalog, "Hyperliquid Index")
info_url = endpoint_url(catalog, "Hyperliquid HyperCore Info")
session = requests.Session()
markets = [args.market] if args.market else discover_markets(session, info_url)
if args.max_markets is not None:
markets = markets[: args.max_markets]
writer = pq.ParquetWriter(args.output, SCHEMA, compression="zstd")
written = 0
try:
for index, market in enumerate(markets, start=1):
print(f"exporting {market} ({index}/{len(markets)})", file=sys.stderr)
for rows in candle_pages(session, index_base_url, market, args.interval, args.start, args.end):
writer.write_table(table_from_rows(rows))
written += len(rows)
if written == 0:
writer.write_table(pa.Table.from_pylist([], schema=SCHEMA))
finally:
writer.close()
print(f"wrote {written} candles for {len(markets)} market(s) to {args.output}")
return 0
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--key-name", required=True, help="Dwellir API key name from `dwellir keys list --toon`")
parser.add_argument("--market", help="One market symbol, for example BTC, @142, or hyna:ETH")
parser.add_argument("--all-markets", action="store_true", help="Discover and export all currently listed markets")
parser.add_argument("--max-markets", type=int, help="Limit discovered markets for smoke tests")
parser.add_argument("--interval", required=True, choices=sorted(VALID_INTERVALS))
parser.add_argument("--start", help="Inclusive ISO-8601 UTC start, for example 2026-03-01T00:00:00Z")
parser.add_argument("--end", help="Exclusive ISO-8601 UTC end, for example 2026-04-01T00:00:00Z")
parser.add_argument("--output", required=True)
return export_parquet(parser.parse_args())
if __name__ == "__main__":
raise SystemExit(main())Run a Single-Market Export
Export BTC 1m candles for March 2026:
python export_ohlcv_parquet.py \
--key-name YOUR_KEY_NAME \
--market BTC \
--interval 1m \
--start 2026-03-01T00:00:00Z \
--end 2026-04-01T00:00:00Z \
--output btc-1m-march-2026.parquetRun an All-Markets Export
Start with a short validation window:
python export_ohlcv_parquet.py \
--key-name YOUR_KEY_NAME \
--all-markets \
--interval 1m \
--max-markets 10 \
--start 2025-07-27T08:50:00Z \
--end 2025-07-27T08:51:00Z \
--output hyperliquid-all-1m-smoke.parquetThen run the full interval export. Omit --end to read through the latest completed bucket:
python export_ohlcv_parquet.py \
--key-name YOUR_KEY_NAME \
--all-markets \
--interval 1m \
--start 2025-07-27T08:00:00Z \
--output hyperliquid-all-1m.parquetThis can return tens of millions of billable candle objects for 1m, and substantially more for 1s. Use your plan limits and expected billing weight when sizing a full-universe run.
Reading the Result
Quick validation with PyArrow:
import pyarrow.parquet as pq
table = pq.read_table("hyperliquid-all-1m-smoke.parquet")
print(table.schema)
print(table.to_pydict())Query the file with DuckDB:
SELECT s, i, count(*) AS rows, min(t) AS first_open, max(t) AS last_open
FROM read_parquet('hyperliquid-all-1m.parquet')
GROUP BY s, i
ORDER BY rows DESC
LIMIT 10;The Parquet schema keeps the same abbreviated field names as the REST payload: s, i, t, T, o, h, l, c, v, q, n, x. Prices and volumes remain decimal strings so downstream tools do not lose precision.
Production Notes
- Keep
start,end, andcursoraligned to the requested interval. - Store progress per market if you are running a full-history all-markets export. The compact script above is intentionally simple and writes one pass.
- Retry transient
429and5xxresponses with exponential backoff before restarting a large job. - For very large exports, write one Parquet file per market or partition by
intervalandmarketso retries and downstream scans stay manageable. - The range API bills by returned candle objects, not by empty sparse buckets or by HTTP page count.
- If you want a simple flat-file export, use the companion guide: Export Hyperliquid OHLCV Candles to CSV.
Export Hyperliquid OHLCV Candles to CSV
Generate CSV files for one Hyperliquid market, or every discovered market for one interval, from Dwellir's OHLCV endpoint.
Build a Hyperliquid Trading App with Builder Codes
Implement builder fee approval, agent wallet sessions, and order placement using Dwellir's Hyperliquid endpoints.