
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.
Prefer a ready-made file?
If you just want the full archive as one file without building a fetcher, use the managed Hyperliquid OHLCV Full-History Exports — download a gzipped CSV 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 CSV 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 CSV
The exporter uses the paginated /v1/candles/range endpoint instead of requesting one bucket at a time. Range pages return sparse finalized candles only, so empty buckets are skipped by the API and do not need special handling in the script.
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 CSV.
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`.
- Write CSV 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 a sparse candle range for one market with cursor pagination
- discover native perp, spot, and HIP-3 market symbols from the Info endpoint
- iterate all discovered markets for one interval and append them to one CSV
- verify the exported CSV before loading it into downstream tooling
Prerequisites
Install Python 3.10+ and requests:
python --version
pip install requestsInstall 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_csv.py:
import argparse
import csv
import json
import subprocess
import sys
from typing import Any
import requests
FIELDS = ["s", "i", "t", "T", "o", "h", "l", "c", "v", "q", "n", "x"]
VALID_INTERVALS = {"1s", "1m", "5m"}
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 export_csv(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]
written = 0
with open(args.output, "w", newline="") as handle:
writer = csv.DictWriter(handle, fieldnames=FIELDS)
writer.writeheader()
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.writerows(rows)
written += len(rows)
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_csv(parser.parse_args())
if __name__ == "__main__":
raise SystemExit(main())Run a Single-Market Export
Export BTC 1m candles for March 2026:
python export_ohlcv_csv.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.csvRun an All-Markets Export
Start with a short validation window:
python export_ohlcv_csv.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.csvThen run the full interval export. Omit --end to read through the latest completed bucket:
python export_ohlcv_csv.py \
--key-name YOUR_KEY_NAME \
--all-markets \
--interval 1m \
--start 2025-07-27T08:00:00Z \
--output hyperliquid-all-1m.csvThis 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.
Verify the Result
Inspect the first rows:
head -5 hyperliquid-all-1m-smoke.csvLoad it with DuckDB:
SELECT s, i, count(*) AS rows, min(t) AS first_open, max(t) AS last_open
FROM read_csv_auto('hyperliquid-all-1m.csv')
GROUP BY s, i
ORDER BY rows DESC
LIMIT 10;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. - The range API bills by returned candle objects, not by empty sparse buckets or by HTTP page count.
- If you need a typed analytics format instead of CSV, use the companion guide: Export Hyperliquid OHLCV Candles to Parquet.
Backfill the Full Hyperliquid OHLCV Archive
Pull the full Hyperliquid OHLCV archive for one market, or fan the same exporter across all discovered markets for one interval, with a resumable multi-process Python exporter.
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.