vike

Recipe 64 of 65

Load a month of Hyperliquid fills into Polars or Postgres within the daily quota

Read the month's file sizes from /archive/datasets, then for each day ask /archive/url for a link right before downloading, keep the rows you need and delete the raw file; a month of fills is about 9 to 11 GB against an allowance of 20 GB of links per 24 hours.

GET /v1/hyperliquid/archive/datasets and GET /v1/hyperliquid/archive/url answer it together. A month of fills is 30 daily Parquet files. Budget first: the index lists every day's bytes, and the sum must fit the allowance of 20 GB of links per user per rolling 24 hours. Then loop over the days: ask /archive/url for one link, download the file at once (the link lasts 300 seconds), keep the rows you need, and delete the raw file before the next day. The full parameter lists are on Archive file index and Archive download link.

Quick answer

Read the month's size first, then run the download loop.

cURL — budget
# days and GB of the month's fills files
curl -s -H "X-API-Key: $VIKE_API_KEY" \
  "https://data.vike.io/v1/hyperliquid/archive/datasets" \
  | jq '[.dates[] | select(.date | startswith("2026-09")) | .streams.fills.bytes] | {days: length, gb: (add / 1e9)}'
Python + Polars
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 of links")  # must fit 20 GB per rolling 24 hours

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 429 here says how much was issued in the last 24 hours
    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")

Example response

Captured from the live API on 2026-10-24 at 17:17 UTC: one link from /archive/url. The signed token in url is replaced, because a live link is a credential for 300 seconds.

bytes is the file's size, the amount this link counts against the allowance; rows its fills. On the same day's index, September 2026 was 30 files and 9.09 GB, the 30 days to 2026-10-23 were 10.54 GB, and the largest single day (2026-10-15) was 0.54 GB.

Example response
{
  "url": "https://data.vike.io/dl/<exp>.<kid>.<sig>/venue=hyperliquid/date=2026-10-23/fills.parquet",
  "expires_at": "2026-10-24T17:22:08Z",
  "expires_in": 300,
  "dataset": "fills",
  "date": "2026-10-23",
  "bytes": 357514873,
  "rows": 11936174
}

Variations

Into Postgres. Write each kept day to CSV and copy it in. Keep trade_id and order_id as numeric(20,0): they are unsigned 64-bit and some trade ids are above the signed bigint range.

Polars — CSV
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")
psql
CREATE TABLE hl_fills (
  fill_ts timestamptz, wallet text, asset text, side text,
  size_token double precision, price double precision, size_usd double precision,
  fee double precision, fee_token text, is_maker boolean,
  order_id numeric(20,0), trade_id numeric(20,0), crossed boolean,
  closed_pnl double precision, dex_name text, start_position double precision
);
\copy hl_fills FROM 'btc-2026-09-01.csv' WITH (FORMAT csv, HEADER true)

The columns are in file order, so the CSV header lines up with the table. An empty start_position (fills before 2026-09-29) loads as NULL.

One link and download in one step. redirect=1 answers 302 with the link in Location; -L follows it.

cURL — download a day
# one day of fills straight to disk
curl -s -L -H "X-API-Key: $VIKE_API_KEY" -o fills-2026-09-01.parquet \
  "https://data.vike.io/v1/hyperliquid/archive/url?dataset=fills&date=2026-09-01&redirect=1"

Try the code with no key first. The free sample has the same 16 columns; run the filter and the Postgres load on it before spending allowance.

cURL — free sample
# one BTC hour of fills, no API key
curl -o fills-sample.parquet "https://data.vike.io/archive/hl-samples/fills-sample.parquet"

Before you rely on it

The allowance counts links, not downloads

Every link issued counts its file's full bytes for 24 hours, whether you download it or not, and all of a user's keys share one allowance. A link asked for and left unused still counts, so ask for each link right before its download, and let a rerun skip the days already kept, as the loop does. Past the allowance the call answers 429 with no Retry-After; ask again once earlier links have aged out.

Download whole files; one file is one UTC day

A link is accepted for 300 seconds from the start of each request, so a reader that opens new requests over HTTP fails part way; download the whole file, then read it locally. The archive starts on 2026-05-01 and a day appears in the early UTC morning after it ends. Both sides of every trade are in each file: filter is_maker = false before summing notional.

Ask your agent

No MCP tool reads the archive: use the files.

Example response
{  "url": "https://data.vike.io/dl/<exp>.<kid>.<sig>/venue=hyperliquid/date=2026-10-23/fills.parquet",  "expires_at": "2026-10-24T17:22:08Z",  "expires_in": 300,  "dataset": "fills",  "date": "2026-10-23",  "bytes": 357514873,  "rows": 11936174}