Hyperliquid historical data: trade history, candles and the order book
Hyperliquid historical data for backtests and research: every fill as one Parquet file a day, any wallet's trade history, candles and tick bars built from executed fills, the standing orders of the order book, and positions as they were at any past snapshot. The samples need no key.
“Give me BTC 1-minute candles on Hyperliquid for last Monday.”
hl_ohlcv“Get me a download link for yesterday's Hyperliquid fills file.”
hl_archiveHow far back each Hyperliquid dataset goes
Every date below is read off the code that serves it.
| Data | Delivery | From | Kept | Where |
|---|---|---|---|---|
| Fills of one wallet | REST · MCP | the end of 2025-12-31 | no expiry | Open → |
| Fills of every wallet, one file a day | Parquet | 2026-05-01 | every day since | Open → |
| Candles, time bars | REST · MCP · WebSocket | the end of 2025-12-31 | no expiry | Open → |
| Candles, tick bars | REST · MCP | the end of 2025-12-31 | built from the fills | Open → |
| Position snapshots | REST · MCP | 2026-05-01 | 1 year | Open → |
| Position snapshots, one file a day | Parquet | 2026-05-08 | every day since | Open → |
| Standing orders | REST · MCP | 2026-07-29 | 5-minute snapshots from 2026-10-23 21:45 UTC | Open → |
| Liquidations | REST · MCP · WebSocket | exact from 2026-08-03 | estimated before | Open → |
Every fill of any wallet
Side, price, size, fee, maker or taker and the PnL each fill closed, newest first.
Fills
Per-wallet fills with side, price, notional, fee, maker flag and closed PnL.
- Route
/v1/hyperliquid/wallets/{address}/fills- Cadence
- Continuous; archive one file per UTC day
- Coverage
- API: a wallet's fills, newest first, keyset-paged on (fill_ts, trade_id) — pass nextCursor back as cursor, limit up to 5,000; filter by symbol, side, start and end. Builder-routed fills have their own route,
/builders/{builder}/fills. Archive: fills.parquet per UTC day from 2026-05-01 (CC BY 4.0), every fill of every wallet. Each file comes as a temporary link from /archive/url; /archive/datasets lists the days and the file's own columns. - Rows
- MCP
hl_wallet_activity- Stream
hl.fills(from $1,000 unless you setmin_usd)
Fields
| Field | Type | Description |
|---|---|---|
ts | string | Fill time, ISO-8601 UTC with milliseconds |
symbol | string | Symbol; HIP-3 DEX symbols carry their DEX prefix, spot pairs are HL's @index |
side | string | buy | sell |
direction | string | buy | sell (mirrors side) |
size_token | number | Size in coin units |
price | number | Fill price |
size_usd | number | Notional, USD |
fee | number | Fee paid in fee_token; negative is a rebate |
fee_token | string | Currency the fee is paid in |
is_maker | boolean | Maker (true) or taker (false) |
crossed | boolean | The order crossed the book |
closed_pnl | number | Realized PnL closed by this fill, USD |
order_id | integer | Venue order id |
trade_id | integer | Venue trade id |
dex_name | string | HL, or the HIP-3 DEX |
Live: one wallet's fills
Fills tape · /wallets/{address}/fills
/v1/hyperliquid/wallets/{address}/fills?symbol={symbol}&limit={limit}The tape replays the fills captured for the most active wallet in the captured /traders reply. The builder's address chip is that same wallet.
GET /v1/hyperliquid/wallets/{address}/fills · Any wallet's trade history, fill by fill
- CLI
vike-data hl wallet-activity- MCP
hl_wallet_activity· 1 credit- Cost
- 1 request of the hourly limit
Raw fills for one wallet.
Parameters
| Parameter | In | Description |
|---|---|---|
addressrequired | path | The wallet |
symbol | query | HL ticker |
start | query | ISO-8601 UTC bounds |
end | query | ISO-8601 UTC bounds |
side | query | buy | sell |
limit | query | Default 100, max 5000 |
cursor | query | Keyset cursor |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/hyperliquid/wallets/0xbf342cf5d1e4a4bb91b171c15e3cdbe82da3fcff/fills?symbol=BTC&limit=100"
import os, requests
API = "https://data.vike.io/v1/hyperliquid"
address = "0xbf342cf5d1e4a4bb91b171c15e3cdbe82da3fcff"
rows, cursor = [], ""
while True: # newest first, 5,000 a page: send nextCursor back until it is null
page = requests.get(
f"{API}/wallets/{address}/fills",
params={"symbol": "BTC", "limit": 5000, "cursor": cursor},
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
timeout=30,
).json()
rows += page["items"]
cursor = page["nextCursor"]
if not cursor:
break
print(len(rows), "BTC fills;", sum(f["closed_pnl"] for f in rows), "USD realized")
vike-data hl wallet-activity 0xbf342cf5d1e4a4bb91b171c15e3cdbe82da3fcff --symbol BTC --limit 100
# any MCP client; or from the terminal:
vike-data call hl_wallet_activity --args '{"view": "fills", "address": "0xbf342cf5d1e4a4bb91b171c15e3cdbe82da3fcff", "symbol": "BTC", "limit": 100}'
Every fill of every wallet, one file a day: the Parquet archive.
Hyperliquid historical candles (OHLCV) from executed fills
Time bars and tick bars, perp and spot, the core venue and the HIP-3 DEXs: over REST, as an NDJSON export, and live.
Candles
Hyperliquid OHLCV built from executed fills — time bars and N-trade tick bars.
- Route
/v1/hyperliquid/ohlcv- Cadence
- Bars close every interval; closed bars also stream
- Coverage
- Time bars 1m 5m 15m 30m 1h 4h 1d from fill rollups (no TTL); tick bars (Nt, e.g. 15t = 15 trades) walked from raw fills, kept without expiry back to 2025-12-31. market=perp | spot; HL core plus the HIP-3 DEXs (dex=XYZ, CASH, HYNA, KM, FLX, VNTL, PARA, MKTS, IO). Closed bars stream on the hl.candles channel of wss://stream.vike.io/v1/stream/hl; /ohlcv/export streams a window as NDJSON.
- Rows
- MCP
hl_ohlcv
Fields
| Field | Type | Description |
|---|---|---|
ts | integer | Bar open time, epoch milliseconds UTC |
open | number | First fill price in the bar |
high | number | Highest fill price |
low | number | Lowest fill price |
close | number | Last fill price in the bar |
volume | number | Volume in coin units |
volume_usd | number | Volume, USD |
n_trades | integer | Fills in the bar |
source | string | Envelope: the data that answered — 1m fills rollup, 1h fills rollup, or fills for tick bars |
Live: BTC, hour by hour
Candles · /ohlcv
/v1/hyperliquid/ohlcv?symbol=BTC&interval=1h&market=perp&limit=48Captured for BTC, ETH, SOL, HYPE at 1m, 15m, 1h, 4h, 1d, plus 15t tick bars for BTC: 48 bars each.
Intervals
Time bars 1m, 5m, 15m, 30m, 1h, 4h, 1d come from minute and hourly rollups kept from the end of 2025-12-31. Tick bars (15t: one bar per N trades) are built from the raw fills, kept without expiry; the answer's source says which answered.
Markets and DEXs
market=perp | market=spot. dex=HL is the core venue (perp and spot); the HIP-3 builder-deployed perp DEXs, XYZ, CASH, HYNA, KM, FLX, VNTL, PARA, MKTS, IO among them, are read live from the fills, so a newly deployed DEX works without a release.
Three ways out
REST /ohlcv, up to 5,000 bars a call, start and end as ISO-8601 UTC or epoch ms. Bulk /ohlcv/export as NDJSON. Live closed bars on hl.candles (time intervals only).
Build an /ohlcv request
/v1/hyperliquid/ohlcv?symbol={symbol}&interval={interval}&market={market}&limit={limit}… or many coins per hour with /symbols/hourly
Many coins at once, hour by hour: close, open interest, funding rate and premium, up to 400 days in one call, from /symbols/hourly. Funding history and its route card: Funding rates.
/v1/hyperliquid/symbols/hourly?symbols={symbols}&days={days}NDJSON export
The first line names what answered: symbol, market, dex, interval, tz, source; every following line is one bar. The file comes as an attachment named hl_<symbol>_<interval>.ndjson.
curl -H "X-API-KEY: $VIKE_API_KEY" -o hl_BTC_1h.ndjson \
"https://data.vike.io/v1/hyperliquid/ohlcv/export?symbol=BTC&interval=1h&start=2026-01-01T00:00:00Z"
GET /v1/hyperliquid/ohlcv · Candles and tick bars from executed fills
- CLI
vike-data candles hl- MCP
hl_ohlcv· 1 credit- Cost
- 1 request of the hourly limit
Hyperliquid candles, derived from executed fills.
Parameters
| Parameter | In | Description |
|---|---|---|
symbolrequired | query | Perp ticker (BTC), spot token (HYPE) or raw index (@107) |
interval | query | 1m | 5m | 15m | 30m | 1h | 4h | 1d, or Nt for N-trade bars. Default 1h |
market | query | perp | spot. Default perp |
dex | query | HL core or a HIP-3 perp DEX, e.g. XYZ. Default HL. Builder DEXs are added over time, so do not hard-code a list |
start | query | ISO-8601 UTC or epoch ms, inclusive |
end | query | ISO-8601 UTC or epoch ms, exclusive |
limit | query | Default 500, clamped to 5000 |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/hyperliquid/ohlcv?symbol=BTC&interval=1h&limit=48"
import os, requests
r = requests.get(
"https://data.vike.io/v1/hyperliquid/ohlcv?symbol=BTC&interval=1h&limit=48",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data candles hl BTC --interval 1h --limit 48
# any MCP client; or from the terminal:
vike-data call hl_ohlcv --args '{"symbol": "BTC", "interval": "1h", "limit": 48}'
GET /v1/hyperliquid/ohlcv/export · The same bars as NDJSON, 5,000 a call
- CLI
vike-data candles hl- MCP
hl_ohlcv· 1 credit- Cost
- 2 requests of the hourly limit
Stream a window as NDJSON. Billed 2 credits up front, because the response is a stream and its row count is unknown when the usage is recorded.
Parameters
| Parameter | In | Description |
|---|---|---|
symbolrequired | query | As on /ohlcv |
interval | query | As on /ohlcv. Default 1h |
market | query | As on /ohlcv. Default perp |
dex | query | As on /ohlcv. Default HL |
start | query | As on /ohlcv |
end | query | As on /ohlcv |
limit | query | Defaults to the maximum, 5000 |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/hyperliquid/ohlcv/export?symbol=BTC&interval=1h&start=2026-01-01T00%3A00%3A00Z"
import os, requests
with requests.get(
"https://data.vike.io/v1/hyperliquid/ohlcv/export",
params={"symbol": "BTC", "interval": "1h", "start": "2026-01-01T00:00:00Z"},
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
stream=True, timeout=120,
) as r:
r.raise_for_status()
lines = r.iter_lines()
print("header:", next(lines)) # symbol, market, dex, interval, tz, source
for line in lines: # one bar per line
print(line)
vike-data candles hl BTC --view export --interval 1h --start 2026-01-01T00:00:00Z
# any MCP client; or from the terminal:
vike-data call hl_ohlcv --args '{"view": "export", "symbol": "BTC", "interval": "1h", "start": "2026-01-01T00:00:00Z"}'
Hyperliquid Parquet archive: fills and positions, one file per UTC day
Fills from 2026-05-01, positions from 2026-05-08. A file has no URL of its own: you ask the API for a link, valid 7 days.
Backtest on full-day fills
If you are testing a signal against what actually traded, the fills file is every fill of the day with price, size, fee, whether the wallet was maker or taker and the realized PnL the node booked, sorted by asset so one asset is a slice of the file.
Positioning research without lookahead
If your question is how the crowd was positioned at a moment in the past, the positions files carry every snapshot with the size tier the wallet had when the row was written, so a historical row is not graded with today's label.
Whole-book snapshots without paging the API
If you need every open position at once, the latest positions file is one download of the current book, replaced every 5 minutes, instead of paging the positions route for minutes.
- Find the day
/archive/datasets lists every published day with each file's rows and bytes, and the snapshot count of a positions day.
- Ask for a link
/archive/url names one file and answers with a link that needs no key, valid 7 days.
- Download it whole
Then read it locally with polars, pandas or DuckDB. The download is not counted against your hourly limit.
Download a day
# 1. Which days exist, with rows, bytes and each positions day's snapshot count
curl -H "X-API-KEY: $VIKE_API_KEY" "https://data.vike.io/v1/hyperliquid/archive/datasets"
# 2. A link to one file: it needs no key, names that file only and works for 7 days
URL=$(curl -s -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/hyperliquid/archive/url?dataset=fills&date=2026-10-23" | jq -r .url)
# 3. Download the whole file, then read it locally
curl -f -o fills-2026-10-23.parquet "$URL"
import os, requests
import polars as pl
ask = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/url?dataset=fills&date=2026-10-23",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
timeout=30,
)
ask.raise_for_status()
# the link is the credential: no key header. Download the whole file, THEN query it
with requests.get(ask.json()["url"], stream=True, timeout=(10, 300)) as r:
r.raise_for_status()
with open("fills-2026-10-23.parquet", "wb") as f:
for chunk in r.iter_content(1 << 20):
f.write(chunk)
print(pl.scan_parquet("fills-2026-10-23.parquet")
.filter(pl.col("asset") == "BTC")
.select(pl.len(), pl.col("size_usd").sum())
.collect())
vike-data hl archive # the days and their files
vike-data hl archive --view url --dataset fills --date 2026-10-23 # one link
-- fills-2026-10-23.parquet downloaded first (see the cURL tab), then read locally.
-- Both wallets of a trade are rows: the taker side (crossed) counts each trade once.
SELECT asset,
count(*) AS fills,
sum(size_usd) FILTER (WHERE crossed) AS taker_volume_usd
FROM 'fills-2026-10-23.parquet'
GROUP BY asset
ORDER BY taker_volume_usd DESC
LIMIT 10;
-- positions-2026-10-23.parquet: the same link with dataset=positions, downloaded first.
-- Net and open notional per asset at the day's last snapshot (direction: 1 long, -1 short).
SELECT asset,
sum(size_usd * direction) AS net_usd,
sum(size_usd) AS open_usd
FROM 'positions-2026-10-23.parquet'
WHERE snapshot_ts = (SELECT max(snapshot_ts) FROM 'positions-2026-10-23.parquet')
GROUP BY asset
ORDER BY open_usd DESC
LIMIT 10;
- Asking for a link is one ordinary request on your hourly budget; the download itself spends none, and there is no download quota.
- A link works for any number of requests until it expires, so DuckDB or pyarrow can read byte ranges straight from it; for repeated queries, download the file once.
date=latestwithdataset=positionsis one file of the current open positions of every asset. The current open positions of every asset, one row per (wallet, asset, direction), refreshed about every 5 minutes. The columns are those of the dailypositionsfile (meanings underschema.positions).snapshot_tsis per row: the ingestion cycle that last wrote the position. A position that closed within the last 15 minutes can still be present with an oldersnapshot_ts, the same rule as /v1/hyperliquid/positions; keep the rows at the file'ssnapshot_tsfor the newest cycle only. There is noopened_at: /v1/hyperliquid/positions fills it per page, and joining it here costs 3x the rest of the run. Sorted by (asset, wallet, direction) in row groups of about 65,000 rows with min/max statistics (one asset is 1-2 of them). The file is REPLACED every ~5 minutes: download it whole (about 30 MB); a ranged read can straddle a replacement and fail, so retry it.- Licence CC BY 4.0: use it for anything, credit Vike.
Which days exist, and what is in each file
- Route
GET /v1/hyperliquid/archive/datasets- CLI
vike-data hl archive- MCP
hl_archive· 1 credit- Cost
- 1 request of the hourly limit
Index of the Hyperliquid Parquet file archive: which UTC days exist and what is in each file.
One Parquet file per UTC day for fills (every fill) and positions (every 5-minute snapshot of every open position), from 2026-05-01. dates is newest first, each {"date": "YYYY-MM-DD", "streams": {"fills": {"rows", "bytes"}, "positions": {"rows", "bytes", "snapshots"}}}; snapshots is how many distinct 5-minute snapshots that day holds. schema lists every column of both datasets with its type and meaning. A day appears once it is complete (yesterday's files are published in the early UTC morning).
latest.positions is ONE file with the current open positions of every asset, replaced every 5 minutes (snapshot_ts, generated_at, rows, bytes, assets, note).
No daily or latest file has a direct URL and the folder cannot be listed: url_endpoint (/v1/hyperliquid/archive/url) answers with a 7-day link for exactly one file. The one exception is the free samples: samples_base is their public folder, and its files (manifest.json names them) are read with a plain GET or HEAD and no API key.
Parameters
None.
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/hyperliquid/archive/datasets"
import os, requests
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/datasets",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data hl archive
# any MCP client; or from the terminal:
vike-data call hl_archive --args '{"view": "datasets"}'
A 7-day download link for one file
- Route
GET /v1/hyperliquid/archive/url- CLI
vike-data hl archive- MCP
hl_archive· 1 credit- Cost
- 1 request of the hourly limit
A download link to ONE archive file, valid 7 days.
{"url", "expires_at", "expires_in", "dataset", "date", "bytes", "rows"}. The url is a presigned Cloudflare R2 link (host <account>.r2.cloudflarestorage.com): it needs no API key (the link is the credential), names exactly this file, and works for expires_in seconds (604800, 7 days) for any number of requests, so a reader that fetches byte ranges over HTTP (DuckDB, pyarrow) can use it directly. Once it expires, R2 answers 403: ask again. The call costs one credit and one request of the hourly budget like any route; the download itself spends none, and there is no download quota.
Parameters
| Parameter | In | Description |
|---|---|---|
datasetrequired | query | fills or positions. Anything else returns 400 naming both. |
daterequired | query | YYYY-MM-DD of a published UTC day, or latest (positions only: the file of the current open positions, replaced every 5 minutes). A day that is not published returns 400 naming the range that is. |
redirect | query | 1 or true answers 302 with the link in Location; the JSON body is still sent, so a client that does not follow redirects keeps the answer. |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/hyperliquid/archive/url?dataset=fills&date=2026-10-23"
import os, requests
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/url?dataset=fills&date=2026-10-23",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data hl archive --view url --dataset fills --date 2026-10-23
# any MCP client; or from the terminal:
vike-data call hl_archive --args '{"view": "url", "dataset": "fills", "date": "2026-10-23"}'
What is in a file
Every column of both files, as the exporter writes it.
fills.parquet · 16 columns
Every fill of every wallet on the day, one row per (wallet, fill). Sorted by asset, fill_ts, trade_id, wallet. A fill is identified by (wallet, fill_ts, trade_id). The two counterparties of a trade share trade_id, so a trade appears twice, once per wallet.
| Field | Type | Description |
|---|---|---|
fill_ts | DateTime64(3, 'UTC') | Execution time of the fill, UTC, millisecond precision. |
wallet | String | Trader address, lowercase 0x-hex. |
asset | String | Instrument as Hyperliquid names it: BTC on the main dex, dex:NAME (e.g. xyz:SP500) for a builder-deployed perp. |
side | String | buy or sell: the direction of THIS wallet's order. |
size_token | Float64 | Fill size in base-asset units, unsigned (the direction is side). |
price | Float64 | Execution price, USD. |
size_usd | Float64 | Notional of the fill, size_token * price, USD. |
fee | Float64 | Fee charged in fee_token; negative is a maker rebate. |
fee_token | String | Token the fee is denominated in, e.g. USDC. |
is_maker | Bool | True when this wallet's order was resting on the book. |
order_id | UInt64 | Hyperliquid order id (oid) of this wallet's order. |
trade_id | UInt64 | Hyperliquid trade id (tid). Both counterparties of one trade carry the same value; a fill is identified by (wallet, fill_ts, trade_id). |
crossed | Bool | True when this fill crossed the book (the taker side). |
closed_pnl | Float64 | Realized PnL in USD the node booked on this fill; 0 for a fill that opens or adds to a position. |
dex_name | String | Perp dex the asset trades on: HL for the main dex, else the builder dex (e.g. XYZ). |
start_position | Float64 (nullable) | Signed position in the asset immediately BEFORE this fill (negative is short). NULL for fills before 2026-09-29, when the column was added; 0.0 is a real value (flat before the fill). |
positions.parquet · 19 columns
Every open position at every snapshot of the day (about every 5-7 minutes), one row per (wallet, asset, snapshot). Sorted by asset, wallet, snapshot_ts. Snapshots are not evenly spaced, and a source outage that could not be rebuilt leaves a gap that is published as it is: snapshots on the day's entry counts them. A position held across snapshots appears in each. account_value and pnl_cohort are as of the START of the row's clock hour (equity and fills strictly before it; pnl_cohort is one hour earlier than the API's point-in-time label; meanings under schema.positions). Hours the source rebuilt by replaying fills instead of reading a node state file are listed in reconstructed_hours on the day's entry (reconstructed, not measured: margin_used and liquidation_price are 0, state_ts is 1970-01-01). Sizes matched the next real snapshot at 97 % or better, except the hours also listed in reconstructed_lower_match_hours, which matched at 83-97 %.
| Field | Type | Description |
|---|---|---|
snapshot_ts | DateTime('UTC') | Snapshot instant, UTC, whole minutes: the START of the ingestion cycle that wrote the row, not the instant of the node state it reads (typically 5-10 minutes earlier). Cycles are usually 5-7 minutes apart and irregular; a source outage leaves a longer gap. The day's snapshots count in the manifest says how many there are. |
wallet | String | Trader address, lowercase 0x-hex. |
asset | String | Instrument as Hyperliquid names it: BTC on the main dex, dex:NAME (e.g. xyz:SP500) for a builder-deployed perp. Called symbol in /v1/hyperliquid/positions. The prefix is authoritative: judge the dex by it, not by dex_name. |
direction | Int8 | 1 long, -1 short. The only place the side is stored: size_token and size_usd are never negative. |
size_token | Float64 | Position size in base-asset units, unsigned (the side is direction). |
size_usd | Float64 | Notional of the position at mark_price, size_token * mark_price, USD, unsigned. |
entry_price | Float64 | Average entry price of the position, USD. |
mark_price | Float64 | Mark price when the row was written, USD. |
liquidation_price | Float64 | Liquidation price, USD; 0 when the position has none (e.g. a well-collateralised cross-margin position). |
unrealized_pnl | Float64 | Unrealized PnL in USD, direction * (mark_price - entry_price) * size_token. |
leverage | Float32 | Leverage of the position as a multiple (10 = 10x). 0 when none was recorded, which happens on cross-margin rows. |
margin_used | Float64 | Margin the position ties up, USD: size_usd / leverage, or size_usd itself where leverage is 0. |
size_cohort | String | Account-value size tier of the wallet WHEN THE ROW WAS WRITTEN (shrimp, 2x shrimp, fish, ... up to 4x whale: 12 absolute-dollar thresholds assigned at ingestion), as stored: a point-in-time label, not today's grading. The same field and vocabulary as size_cohort in /v1/hyperliquid/positions. |
leverage_type | String | cross or isolated. |
dex_name | String | Perp dex the asset trades on: HL for the main dex, else the builder dex (e.g. XYZ). Rows written while a clearinghouse was not yet registered hold the literal clsN (e.g. cls10) and keep it; the asset prefix is authoritative. |
state_ts | DateTime('UTC') | The instant the state-derived columns (size, entry, mark, unrealized_pnl, leverage, margin_used, direction) describe, read from the node's state file; snapshot_ts is the WRITE time of the ingestion cycle, typically 5-10 minutes later (liquidation_price is re-read from the node at write time and belongs to snapshot_ts). 1970-01-01 00:00:00 means the row came from no state file (the per-wallet API fallback) or was written before 2026-08-22, when the column was added. Do not anchor a projection of the row on it: use snapshot_ts. |
liq_progress | Float64 | How far the mark has moved from entry toward liquidation, clamped to 0..1: 0 at or in profit versus entry (or no liquidation price), 1 at or through the liquidation price. When a cross-margin liquidation price has drifted past entry it falls back to liquidation_price / mark_price (long) or mark_price / liquidation_price (short). The same SQL as liq_progress in /v1/hyperliquid/positions. |
account_value | Float64 (nullable) | Native Hyperliquid perp account value of the wallet, USD (the quantity /v1/hyperliquid/wallets/{address} serves as equity.account_value), AS OF THE START OF THE ROW'S CLOCK HOUR: the wallet's last snapshot of the previous hour (a row at 10:37 carries the 09:5x value). Builder-DEX account equity is NOT included, so a wallet trading only dex:NAME assets usually reads 0. In latest, as of the start of the hour of the file's newest snapshot (an older hour while the hourly rollup lags), so on the rows of positions that closed in the last 15 minutes it can be up to 15 minutes later than the row. NULL when unknown: the wallet had no snapshot in that previous hour (e.g. its first position opened in the row's hour); the stored value is a reconstruction, not a measurement (hours rebuilt from fills, and the values the 2026-08-10 repair rewrote); or the row is a builder-DEX position (dex_name other than HL) whose value comes from an hour up to 2026-08-10 00:48 UTC (the repair seam): until then a builder-DEX-only wallet's empty native account was stored as a non-equity figure the repair did not catch every time, and a genuine balance cannot be told apart from a fabricated one, so genuine values are withheld on those rows too. The same wallet's HL rows in the same snapshot keep the value; only its builder-DEX rows read NULL. |
pnl_cohort | String | Realized-PnL percentile cohort of the wallet AS OF THE START OF THE ROW'S CLOCK HOUR: graded on fills strictly before the row's hour (the hourly label of the previous hour). This is ONE HOUR EARLIER than the label /v1/hyperliquid/positions returns for the same row in historical mode (start given), where a row graded from the hourly label table carries pnl_cohort_basis = point_in_time: that label is the row's own hour and so counts the fills of up to 59 minutes after the row. In latest, as of the start of the hour of the file's newest snapshot (an older hour while the label rollup lags), so on the rows of positions that closed in the last 15 minutes it can count up to 15 minutes of fills after the row. unknown for a wallet with fewer than 10 closed trades then, or with no realized record that hour; never empty. |
Try it with no key
One BTC hour of fills and one BTC position snapshot, the same columns as the archive.
https://data.vike.io/archive/hl-samples/fills-sample.parquethttps://data.vike.io/archive/hl-samples/positions-sample.parquethttps://data.vike.io/archive/hl-samples/manifest.json
Standing orders: what was resting, at any instant
Limit orders on the book and the stops and take-profits waiting to trigger, for a coin or a wallet.
What was resting on a market at a 5-minute snapshot: every order, with its price, size, side and type. The snapshots begin 2026-10-23 21:45 UTC; earlier instants, back to 2026-07-29, are rebuilt from checkpoints about 12 minutes apart, and a rebuild can miss short-lived orders. Each answer's source says which feed answered: l4_5m or state_12m.
/wallets/{address}/orders is one wallet's order history (what was resting, filled or canceled) on the Whale tracker; /symbols/{symbol}/order-clusters is where a coin's stops and limits cluster, on the Liquidations page.
Every order resting at one instant
- Route
GET /v1/hyperliquid/orders- CLI
vike-data hl orders- MCP
hl_orders· 1 credit- Cost
- 1 request of the hourly limit
The orders resting at one instant: every order in a 5-minute snapshot, or a rebuild from the sightings around it when no complete 5-minute snapshot exists.
Returns snapshotTs, source (l4_5m | state_12m), orders, nextCursor. The 5-minute snapshots start at 2026-10-23 21:45 UTC; before that, and after a stalled feed's last snapshot, the answer is rebuilt from the 12-minute checkpoints. A rebuilt answer for a past at lists only the orders seen before the instant and again after it, so short-lived orders can be missing, and its snapshotTs is the at floored to 5 minutes; an at inside an outage of either feed is answered the same way. Rows in key order (dex, symbol, side, oid); the cursor pins the snapshot.
Parameters
| Parameter | In | Description |
|---|---|---|
symbol | query | HL perp ticker, e.g. BTC or xyz:TSLA. symbol and/or wallet is required |
wallet | query | One wallet. symbol and/or wallet is required |
at | query | ISO-8601 UTC, floored to 5 minutes. Default: the newest snapshot. Not before 2026-07-29, not in the future |
oid | query | One order id |
order_type | query | Limit | Stop Market | Stop Limit | Take Profit Market | Take Profit Limit | Trailing Stop Market |
created_from | query | ISO-8601 UTC. Placed at or after |
created_to | query | ISO-8601 UTC. Placed at or before |
limit | query | Default 100, max 1000 |
cursor | query | nextCursor from the previous page, verbatim |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/hyperliquid/orders?symbol=BTC&limit=100"
import os, requests
r = requests.get(
"https://data.vike.io/v1/hyperliquid/orders?symbol=BTC&limit=100",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data hl orders --symbol BTC --limit 100
# any MCP client; or from the terminal:
vike-data call hl_orders --args '{"view": "open", "symbol": "BTC", "limit": 100}'
Closed candles on a WebSocket
The bars of /ohlcv, pushed the moment they close.
- One URL,
wss://stream.vike.io/v1/stream/hl: send your key in theX-API-KEYheader, then one subscribe frame per channel. - The server pings every 15 s: answer with pong. 1 connection per key on this path, 100 subscriptions on one connection.
- Delivery is at least once: dedupe on event.event_id. Dropped? Subscribe again with your last
cursor: you get what you missed, up to 3 days back; without a socket,/v1/hyperliquid/stream-eventsreads the same events. - A channel runs while someone subscribes to it; on a quiet channel the first events come with the next pass.
hl.candles
Every closed bar of every coin that traded, at the time intervals of /ohlcv, read from the same rollups: the bars of /ohlcv, pushed.
- Path
- wss://stream.vike.io/v1/stream/hl
- Filters
- coin, dex, interval, market
Filters
| Field | Type | Description |
|---|---|---|
coin | list ≤ 200 | The symbol, e.g. BTC or xyz:TSLA; on hl.candles a spot token or its @index too. |
dex | list ≤ 50 | HL, the core venue, or a HIP-3 builder DEX. |
interval | list ≤ 50 | The bar's interval. One of: 15m, 1d, 1h, 1m, 30m, 4h, 5m. |
market | list ≤ 50 | perp or spot. One of: perp, spot. |
Subscribe
import json, os
from websockets.sync.client import connect # websockets >= 14
with connect("wss://stream.vike.io/v1/stream/hl",
additional_headers={"X-API-KEY": os.environ["VIKE_API_KEY"]}) as ws:
ws.send(json.dumps({"action": "subscribe", "id": "bars", "channel": "hl.candles",
"filter": {"coin": ["BTC"], "interval": ["1m"]}}))
for message in ws:
frame = json.loads(message)
if frame["type"] == "ping":
ws.send(json.dumps({"action": "pong"}))
elif frame["type"] == "event":
e = frame["event"]
print(json.dumps(e)) # keep frame["cursor"] to resume after a drop
vike-data stream hl --channel hl.candles --filter coin=BTC --filter interval=1m # prints NDJSON, reconnects by itself
{
"action": "subscribe",
"id": "bars",
"channel": "hl.candles",
"filter": {
"coin": [
"BTC"
],
"interval": [
"1m"
]
}
}
The historical data routes
Each one is a card above; every parameter of every route is also in the API reference.
| Route | What it answers | Reference |
|---|---|---|
GET /v1/hyperliquid/wallets/{address}/fills | Any wallet's trade history, fill by fill | API reference → |
GET /v1/hyperliquid/ohlcv | Candles and tick bars from executed fills | API reference → |
GET /v1/hyperliquid/ohlcv/export | The same bars as NDJSON, 5,000 a call | API reference → |
GET /v1/hyperliquid/archive/datasets | Which days exist, and what is in each file | API reference → |
GET /v1/hyperliquid/archive/url | A 7-day download link for one file | API reference → |
GET /v1/hyperliquid/orders | Every order resting at one instant | API reference → |
Historical data questions, answered with code
Each with the sentence to ask your AI instead. Every block was run against the live API on 2026-11-03.
Download every Hyperliquid fill for a day as Parquet and load it in DuckDB
Ask /v1/hyperliquid/archive/url?dataset=fills&date=YYYY-MM-DD for a 7-day link to the day's Parquet file, download it whole, and query it in DuckDB. The 2026-10-23 fills file is 0.36 GB (11.9 million rows).
- Uses
/archive/datasets,/archive/url- Verified
- live API, 2026-11-03: 4 calls, all 200
The code
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# ask for the link and download the 2026-10-23 fills file in one step
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/url?dataset=fills&date=2026-10-23&redirect=1",
headers={"X-API-Key": API_KEY},
)
with open("fills-2026-10-23.parquet", "wb") as f:
f.write(r.content)
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# every published day with each file's rows and bytes, newest first
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/datasets",
headers={"X-API-Key": API_KEY},
)
# then: jq '.dates[:7] | map({date, fills: .streams.fills})'
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# name, type and meaning of every column of the fills files
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/datasets",
headers={"X-API-Key": API_KEY},
)
# then: jq '.schema.fills'
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# the link alone
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/url?dataset=fills&date=2026-10-23",
headers={"X-API-Key": API_KEY},
)
# then: jq -r '.url'
Try Hyperliquid fills and positions Parquet with no API key
Download a free sample of Hyperliquid fills and positions as Parquet with no key: one BTC hour of fills and one BTC position snapshot.
- Uses
/archive/datasets- Verified
- live API, 2026-11-03: 4 calls, all 200
The code
import requests
# what the free samples are: the sample day, the files and the schema; no API key
r = requests.get(
"https://data.vike.io/archive/hl-samples/manifest.json",
)
import requests
# one BTC hour of fills; no API key
r = requests.get(
"https://data.vike.io/archive/hl-samples/fills-sample.parquet",
)
with open("fills-sample.parquet", "wb") as f:
f.write(r.content)
import polars as pl
fills = pl.read_parquet("fills-sample.parquet")
print(fills.height, fills.columns) # 16830 rows on the 2026-10-21 sample
print(fills.filter(~pl.col("is_maker")).select(pl.col("size_usd").sum())) # taker side: each trade once
import requests
# one BTC snapshot of open positions; no API key
r = requests.get(
"https://data.vike.io/archive/hl-samples/positions-sample.parquet",
)
with open("positions-sample.parquet", "wb") as f:
f.write(r.content)
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# the archive index: daily files with rows and bytes, the schema, and samples_base
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/datasets",
headers={"X-API-Key": API_KEY},
)
# then: jq '{samples_base, newest: .dates[0]}'
Download the current open positions of every Hyperliquid asset, refreshed every 5 minutes
One Parquet file holds every open position of every wallet, replaced about every 5 minutes; /v1/hyperliquid/archive/url?dataset=positions&date=latest links to it. The file is about 33 MB (32.7 MB and 425,727 positions at 17:41 UTC on 2026-11-03).
- Uses
/archive/datasets,/archive/url,/positions- Verified
- live API, 2026-11-03: 3 calls, all 200
The code
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# ask for the link and download the current open positions of every asset in one step
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/url?dataset=positions&date=latest&redirect=1",
headers={"X-API-Key": API_KEY},
)
with open("positions-latest.parquet", "wb") as f:
f.write(r.content)
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# the snapshot, write time, rows, bytes and asset count of the current file
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/datasets",
headers={"X-API-Key": API_KEY},
)
# then: jq '.latest.positions | {snapshot_ts, generated_at, rows, bytes, assets}'
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# every open BTC position, 5,000 per page, in wallet order
r = requests.get(
"https://data.vike.io/v1/hyperliquid/positions?symbol=BTC&limit=5000",
headers={"X-API-Key": API_KEY},
)
Is a day of Hyperliquid snapshots complete? Count the 5-minute snapshots and spot gaps
Each day's positions file has a snapshots count and a list of the hours that were rebuilt in /v1/hyperliquid/archive/datasets; the gaps inside a day come from the file's own snapshot_ts values. The positions file of 2026-10-23 is 1.98 GB (122.7 million rows, 286 snapshots): the verification run read its first 1 MB, not the whole file.
- Uses
/archive/datasets,/archive/url- Verified
- live API, 2026-11-03: 3 calls, all 200
The code
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# snapshots, rows and rebuilt hours of every published positions day, newest first
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/datasets",
headers={"X-API-Key": API_KEY},
)
# then: jq '[.dates[] | select(.streams.positions) | {date, snapshots: .streams.positions.snapshots, rows: .streams.positions.rows, reconstructed_hours: .streams.positions.reconstructed_hours}]'
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# the days whose positions file holds fewer than 250 snapshots
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/datasets",
headers={"X-API-Key": API_KEY},
)
# then: jq -r '.dates[] | select(.streams.positions) | select(.streams.positions.snapshots < 250) | "\(.date) \(.streams.positions.snapshots)"'
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# ask for the link and download the 2026-10-23 positions file in one step
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/url?dataset=positions&date=2026-10-23&redirect=1",
headers={"X-API-Key": API_KEY},
)
with open("positions-2026-10-23.parquet", "wb") as f:
f.write(r.content)
Load a month of Hyperliquid fills into Polars or your own database
Read the month's file sizes from /archive/datasets, then for each day ask /archive/url for a 7-day link, download it, keep the rows you need and delete the raw file; a month of fills is about 9 to 11 GB, so at most one raw day is on disk at a time. Measured: September 2026 is 30 files and 9.09 GB, the 30 newest days 10.6 GB, 2026-09-01 alone 0.30 GB. The verification run downloaded that first day only, not the whole month.
- Uses
/archive/datasets,/archive/url- Verified
- live API, 2026-11-03: 6 calls, all 200
The code
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# days and GB of the month's fills files
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/datasets",
headers={"X-API-Key": API_KEY},
)
# then: jq '[.dates[] | select(.date | startswith("2026-09")) | .streams.fills.bytes] | {days: length, gb: (add / 1e9)}'
import os
from pathlib import Path
import polars as pl
import requests
API = "https://data.vike.io/v1/hyperliquid"
H = {"X-API-Key": os.environ["VIKE_API_KEY"]}
MONTH = "2026-09"
index = requests.get(f"{API}/archive/datasets", headers=H, timeout=60).json()
days = sorted(d["date"] for d in index["dates"] if d["date"].startswith(MONTH) and "fills" in d["streams"])
need = sum(d["streams"]["fills"]["bytes"] for d in index["dates"] if d["date"] in days)
print(len(days), "days,", round(need / 1e9, 2), "GB to download") # one raw day on disk at a time
for day in days:
out = Path(f"btc-{day}.parquet")
if out.exists():
continue # done on an earlier run: no new link is asked for
ask = requests.get(f"{API}/archive/url", params={"dataset": "fills", "date": day}, headers=H, timeout=60)
ask.raise_for_status() # a 400 names the published range
raw = Path(f"fills-{day}.parquet")
with requests.get(ask.json()["url"], stream=True, timeout=(10, 300)) as r: # the link needs no key
r.raise_for_status()
with raw.open("wb") as f:
for chunk in r.iter_content(1 << 20):
f.write(chunk)
pl.scan_parquet(raw).filter(pl.col("asset") == "BTC").sink_parquet(out) # keep only what you need
raw.unlink() # free the disk before the next day
month = pl.read_parquet(f"btc-{MONTH}-*.parquet")
pl.read_parquet("btc-2026-09-01.parquet").write_csv("btc-2026-09-01.csv", datetime_format="%Y-%m-%dT%H:%M:%S%.3f%:z")
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# one day of fills straight to disk
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/url?dataset=fills&date=2026-09-01&redirect=1",
headers={"X-API-Key": API_KEY},
)
with open("fills-2026-09-01.parquet", "wb") as f:
f.write(r.content)
import requests
# one BTC hour of fills, no API key
r = requests.get(
"https://data.vike.io/archive/hl-samples/fills-sample.parquet",
)
with open("fills-sample.parquet", "wb") as f:
f.write(r.content)
Backtest a trading strategy on Hyperliquid 1-minute candles
Page /v1/hyperliquid/ohlcv with interval=1m and limit=5000, moving start one minute past each page's last candle, and write the bars to a CSV your backtester reads.
- Uses
/ohlcv,/ohlcv/export,/symbols/hourly- Verified
- live API, 2026-11-03: 13 calls, all 200
The code
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# the first page of a backtest window: up to 5,000 BTC minutes from 2026-10-01 00:00 UTC
r = requests.get(
"https://data.vike.io/v1/hyperliquid/ohlcv?symbol=BTC&interval=1m&start=2026-10-01T00:00:00Z&limit=5000",
headers={"X-API-Key": API_KEY},
)
import csv
import os
import requests
H = {"X-API-Key": os.environ["VIKE_API_KEY"]}
URL = "https://data.vike.io/v1/hyperliquid/ohlcv"
start, end = 1788220800000, 1790812800000 # 2026-09-01 to 2026-10-01 UTC, epoch ms, end exclusive
rows = []
while start < end:
r = requests.get(URL, params={"symbol": "BTC", "interval": "1m", "start": start, "end": end, "limit": 5000},
headers=H, timeout=60)
r.raise_for_status()
page = r.json()["candles"]
if not page:
break
rows += page
start = page[-1]["ts"] + 60_000 # one minute after the last bar
with open("btc-1m-2026-09.csv", "w", newline="") as f:
w = csv.DictWriter(f, fieldnames=["ts", "open", "high", "low", "close", "volume", "volume_usd", "n_trades"])
w.writeheader()
w.writerows(rows)
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# 5,000 BTC minutes as NDJSON, saved to a file
r = requests.get(
"https://data.vike.io/v1/hyperliquid/ohlcv/export?symbol=BTC&interval=1m&start=2026-10-01T00:00:00Z&limit=5000",
headers={"X-API-Key": API_KEY},
)
with open("btc-1m-page1.ndjson", "wb") as f:
f.write(r.content)
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# 5,000 hourly BTC bars
r = requests.get(
"https://data.vike.io/v1/hyperliquid/ohlcv?symbol=BTC&interval=1h&limit=5000",
headers={"X-API-Key": API_KEY},
)
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# BTC hourly close, open interest, funding rate and premium for September 2026
r = requests.get(
"https://data.vike.io/v1/hyperliquid/symbols/hourly?symbols=BTC&start=2026-09-01T00:00:00Z&end=2026-10-01T00:00:00Z",
headers={"X-API-Key": API_KEY},
)
Hyperliquid OHLCV from fills, incl. HIP-3 and spot; CSV/Parquet backfill
Candles built from Hyperliquid's own fills for core perps, HIP-3 builder-dex perps (dex=XYZ and the dex-prefixed symbol) and spot (market=spot), from /v1/hyperliquid/ohlcv; page it with start and turn it into CSV with jq.
- Uses
/ohlcv,/ohlcv/latest- Verified
- live API, 2026-11-03: 5 calls, all 200
The code
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# hourly candles of a HIP-3 market: the dex-prefixed symbol and its dex
r = requests.get(
"https://data.vike.io/v1/hyperliquid/ohlcv?symbol=xyz:PLATINUM&dex=XYZ&interval=1h&limit=3",
headers={"X-API-Key": API_KEY},
)
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# a week of hourly HYPE spot candles
r = requests.get(
"https://data.vike.io/v1/hyperliquid/ohlcv?symbol=HYPE&market=spot&interval=1h&limit=168",
headers={"X-API-Key": API_KEY},
)
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# 5,000 BTC minutes from 2026-10-01 as CSV rows: ts, open, high, low, close, volume
r = requests.get(
"https://data.vike.io/v1/hyperliquid/ohlcv?symbol=BTC&interval=1m&start=2026-10-01T00:00:00Z&limit=5000",
headers={"X-API-Key": API_KEY},
)
# then: jq -r '.candles[] | [.ts, .open, .high, .low, .close, .volume] | @csv'
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# 100-trade BTC bars over ten minutes
r = requests.get(
"https://data.vike.io/v1/hyperliquid/ohlcv?symbol=BTC&interval=100t&start=2026-10-23T12:00:00Z&end=2026-10-23T12:10:00Z&limit=3",
headers={"X-API-Key": API_KEY},
)
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# the newest 1-minute candle of every spot pair that traded in the last hour
r = requests.get(
"https://data.vike.io/v1/hyperliquid/ohlcv/latest?market=spot",
headers={"X-API-Key": API_KEY},
)
Build 1-second candles for a Hyperliquid coin
The API's time bars start at 1 minute; build 1-second bars from the archive's daily fills file by keeping the taker row of each trade, ordering by fill_ts and trade_id, and bucketing by second. Downloads one fills day: 0.36 GB for 2026-10-23.
- Uses
/archive/url,/ohlcv- Verified
- live API, 2026-11-03: 3 calls, all 200
The code
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# download one UTC day of every fill: redirect=1 answers with the link and -L follows it
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/url?dataset=fills&date=2026-10-23&redirect=1",
headers={"X-API-Key": API_KEY},
)
with open("fills-2026-10-23.parquet", "wb") as f:
f.write(r.content)
import polars as pl
fills = pl.read_parquet("fills-2026-10-23.parquet") # or the free fills-sample.parquet
bars = (
fills.filter((pl.col("asset") == "BTC") & ~pl.col("is_maker")) # the taker row: one row per trade
.sort("fill_ts", "trade_id")
.group_by_dynamic("fill_ts", every="1s")
.agg(
open=pl.col("price").first(),
high=pl.col("price").max(),
low=pl.col("price").min(),
close=pl.col("price").last(),
volume=pl.col("size_token").sum(),
volume_usd=pl.col("size_usd").sum(),
n_trades=pl.len(),
)
)
print(bars.head())
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# the API's first three BTC minutes of 2026-10-21
r = requests.get(
"https://data.vike.io/v1/hyperliquid/ohlcv?symbol=BTC&interval=1m&start=2026-10-21T00:00:00Z&limit=3",
headers={"X-API-Key": API_KEY},
)
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# 100-trade BTC bars over ten minutes
r = requests.get(
"https://data.vike.io/v1/hyperliquid/ohlcv?symbol=BTC&interval=100t&start=2026-10-23T12:00:00Z&end=2026-10-23T12:10:00Z&limit=500",
headers={"X-API-Key": API_KEY},
)
grid = pl.datetime_range(bars["fill_ts"].min(), bars["fill_ts"].max(), "1s", time_unit="ms", time_zone="UTC", eager=True).alias("fill_ts").to_frame()
full = grid.join(bars, on="fill_ts", how="left").with_columns(
pl.col("close").forward_fill(),
pl.col("volume", "volume_usd", "n_trades").fill_null(0),
).with_columns(pl.col("open", "high", "low").fill_null(pl.col("close")))
Classify each Hyperliquid fill as open, add, reduce, close or flip
Every fill in the archive's fills file carries start_position, the wallet's signed position just before the fill; add the signed fill size to get the position after, and the pair of numbers names the action. Downloads one fills day: 0.36 GB for 2026-10-23.
- Uses
/archive/url- Verified
- live API, 2026-11-03: 2 calls, all 200
The code
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# download one UTC day of every fill: redirect=1 answers with the link and -L follows it
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/url?dataset=fills&date=2026-10-23&redirect=1",
headers={"X-API-Key": API_KEY},
)
with open("fills-2026-10-23.parquet", "wb") as f:
f.write(r.content)
import polars as pl
fills = pl.read_parquet("fills-2026-10-23.parquet") # or the free fills-sample.parquet
side = pl.when(pl.col("side") == "buy").then(1).otherwise(-1)
start, end = pl.col("start_position"), pl.col("end_position")
classified = (
fills.filter(start.is_not_null() & ~pl.col("asset").str.contains("^[@#]")) # perps; NULL before 2026-09-29
.with_columns(end_position=start + side * pl.col("size_token"))
.with_columns(
action=pl.when(start.abs() < 1e-9).then(pl.lit("open"))
.when(end.abs() < 1e-9).then(pl.lit("close"))
.when(start * end < 0).then(pl.lit("flip"))
.when(end.abs() > start.abs()).then(pl.lit("add"))
.otherwise(pl.lit("reduce"))
)
)
print(classified.group_by("action").agg(pl.len().alias("fills")).sort("fills", descending=True))
import requests
# one BTC hour of fills, no API key
r = requests.get(
"https://data.vike.io/archive/hl-samples/fills-sample.parquet",
)
with open("fills-sample.parquet", "wb") as f:
f.write(r.content)
orders = classified.group_by("wallet", "order_id").agg(
pl.col("action").unique().alias("actions"),
pl.col("size_token").sum(),
)
print(orders.filter(pl.col("actions").list.len() > 1)) # orders that, for example, closed and then reopened
print(classified.filter(pl.col("action") == "flip").select("fill_ts", "wallet", "side", "size_token", "start_position", "end_position", "closed_pnl"))
Who traded against whom? Pair both counterparties of every Hyperliquid trade
In the archive's daily fills file both sides of a trade share trade_id; join the buy rows to the sell rows on asset, fill_ts and trade_id to get one row per trade with its buyer and seller. Downloads one fills day: 0.36 GB for 2026-10-23.
- Uses
/archive/url- Verified
- live API, 2026-11-03: 2 calls, all 200
The code
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# download one UTC day of every fill: redirect=1 answers with the link and -L follows it
r = requests.get(
"https://data.vike.io/v1/hyperliquid/archive/url?dataset=fills&date=2026-10-23&redirect=1",
headers={"X-API-Key": API_KEY},
)
with open("fills-2026-10-23.parquet", "wb") as f:
f.write(r.content)
import polars as pl
fills = pl.read_parquet("fills-2026-10-23.parquet") # or the free fills-sample.parquet
key = ["asset", "fill_ts", "trade_id"]
buys = fills.filter(pl.col("side") == "buy").select(*key, "price", "size_token", buyer="wallet", buyer_maker="is_maker")
sells = fills.filter(pl.col("side") == "sell").select(*key, seller="wallet")
trades = buys.join(sells, on=key)
print(trades.height, "trades from", fills.height, "fills")
pairs = (
trades.group_by("buyer", "seller")
.agg(usd=(pl.col("price") * pl.col("size_token")).sum(), trades=pl.len())
.sort("usd", descending=True)
)
print(pairs.head(10)) # the wallet pairs that traded the most notional with each other
print(trades.filter(pl.col("buyer") == pl.col("seller")).height) # 0 on the sample
both_ways = pairs.join(pairs, left_on=["buyer", "seller"], right_on=["seller", "buyer"], suffix="_back")
print(both_ways.sort("usd", descending=True).head(10))
w = "0xa62b923a112d50d03e1e096bbd53422490dac104"
mine = trades.filter((pl.col("buyer") == w) | (pl.col("seller") == w))
other = pl.when(pl.col("buyer") == w).then(pl.col("seller")).otherwise(pl.col("buyer"))
print(mine.group_by(other.alias("counterparty")).agg(pl.len().alias("trades")).sort("trades", descending=True).head(10))
import requests
# one BTC hour of fills, no API key
r = requests.get(
"https://data.vike.io/archive/hl-samples/fills-sample.parquet",
)
with open("fills-sample.parquet", "wb") as f:
f.write(r.content)
Positions of a Hyperliquid wallet or coin at a past time
Pass start and end to /v1/hyperliquid/positions with a symbol or a wallet, and it returns the open positions of the snapshots inside that window, labelled as they stood then.
- Uses
/positions- Verified
- live API, 2026-11-03: 5 calls, all 200
The code
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# every open BTC position in the snapshots between 12:00 and 12:05 UTC on 2026-10-01
r = requests.get(
"https://data.vike.io/v1/hyperliquid/positions?symbol=BTC&start=2026-10-01T12:00:00Z&end=2026-10-01T12:05:00Z&limit=3",
headers={"X-API-Key": API_KEY},
)
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# everything one wallet held in the last snapshot of the 12:00 UTC hour on 2026-10-01
r = requests.get(
"https://data.vike.io/v1/hyperliquid/positions?wallet=0x25773676902085225ae5751075e4161369334897&start=2026-10-01T12:00:00Z&end=2026-10-01T13:00:00Z&limit=500",
headers={"X-API-Key": API_KEY},
)
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# next page: the first page's nextCursor, sent back with the same start and end
URL = "https://data.vike.io/v1/hyperliquid/positions?symbol=BTC&start=2026-10-01T12:00:00Z&end=2026-10-01T12:05:00Z&limit=5000"
NEXT = requests.get(URL, headers={"X-API-Key": API_KEY}).json()["nextCursor"]
r = requests.get(
f"{URL}&cursor={NEXT}",
headers={"X-API-Key": API_KEY},
)
import os
import requests
API_KEY = os.environ["VIKE_API_KEY"] # your key, read from the environment
# the BTC positions of the 3x smart wallets at that time
r = requests.get(
"https://data.vike.io/v1/hyperliquid/positions?symbol=BTC&axis=pnl&cohort=3x_smart&start=2026-10-01T12:00:00Z&end=2026-10-01T12:05:00Z&limit=1000",
headers={"X-API-Key": API_KEY},
)
Hyperliquid historical data FAQ
How do I get Hyperliquid historical data?
How far back do the candles go?
Can I get candles for HIP-3 DEXs and spot pairs?
How do I download a Hyperliquid file?
Why is there no folder to browse?
Do downloads count against my hourly limit?
How far back do the files go?
Why does a positions day sometimes hold fewer snapshots?
How current are the files?
Is there Hyperliquid order book data?
/v1/hyperliquid/orders lists every order resting at one instant for a coin or a wallet, from the node's 5-minute order snapshots, and back to 2026-07-29 rebuilt from checkpoints about 12 minutes apart.