Get a free key
HL Cohorts · Backtests

Backtest on Hyperliquid whale and smart money positions — hour by hour, as they were then

Who was long and who was short on Hyperliquid, every hour: by account size (whales to shrimp) and by realized PnL (smart money to rekt), each hour graded with the label the wallet had then, not today. Cohort data from 7 April 2026, all of it in one request; the Parquet archive adds every 5-minute position snapshot with its labels. Free, over MCP, REST and the CLI.

Ask your AI · Claude, ChatGPT, CursorConnect in a minute →

“Does Hyperliquid's Smart Money Index lead BTC? Use the last 90 days of hourly readings.”

hl_market hl_ohlcv

“What did BTC do in the 6 hours after the 3x rekt cohort was most long? Use point-in-time labels, last 90 days.”

hl_cohort_metrics hl_ohlcv

“Give me the last 30 days of hourly long/short positioning for ETH by account size, as a table I can backtest on.”

hl_cohort_metrics
Measured 2026-11-04

Why “as they were then” matters: today's labels change the answer

A backtest asks: when this cohort was long, what did the price do next? The answer depends on WHO was in the cohort at that hour. The realized-PnL ladder re-grades every wallet as it trades, so a wallet graded 3x rekt today may have been graded winner a month ago. By default a pnl cohort is today's grading applied to every past hour; that lets a backtest know which wallets went on to win or lose, which quants call look-ahead bias. labelBasis=point_in_time gives each hour the label the wallet had at that hour.

Measured on BTC axis=pnl for the same 720 settled hours (2026-10-05 07:00 to 2026-11-04 06:00 UTC), once with each hour's own labels and once with today's: the share of hours in which the cohort sits on the other side (net long under one labelling, net short under the other), and how far the two bias lines are apart on average (bias runs from -1, all short, to +1, all long).

Cohort (BTC, axis=pnl)Hours on the other sideOfAverage gap between the two bias lines
3x rekt32.2% (232)7200.2536
winner29.2% (210)7200.2556
2x winner15.8% (114)7200.1431
3x loser13.1% (94)7200.1976
2x loser9.7% (70)7200.1063
2x smart5.3% (38)7200.0688
smart0.1% (1)7200.0568
3x smart0.0% (0)7200.1162
3x winner0.0% (0)7200.0623
loser0.0% (0)7200.0648
rekt0.0% (0)7200.064
2x rekt0.0% (0)7200.0304
unknown0.0% (0)7200.0485
BTC · 720 hours · 2026-10-05 07:00 to 2026-11-04 06:00 UTC · measured 2026-11-04

3x rekt sat on the other side in 32.2% of hours (232 of 720).

each hour's own labels (labelBasis=point_in_time)today's labels (current)an hour on the other side
+10-1 Oct 05Nov 04

Ask for each hour's own labels

python
import os, requests

r = requests.get(
    "https://data.vike.io/v1/hl-cohorts/symbols/BTC/cohort-metrics",
    params={"axis": "pnl", "labelBasis": "point_in_time", "hours": 720},   # 30 days, each hour labelled as it was
    headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
    timeout=60,
)
m = r.json()
assert m["label_computed"]["lookahead"] is False   # the response says how its labels were made: check it
print("cohort data from", m["data_from"])          # the first hour the labels are correct: nothing earlier is served
for row in m["metrics"][:13]:                      # the newest hour, one row per cohort
    print(row["ts"], row["cohort"], row["bias"], row["label_basis"])
Coverage

What there is for a backtest

Cohort data from 7 April 2026: the first hour the labels are correct. Start every backtest there or later.
SeriesWhereFromGrainOne requestLabels
Long/short by account size or position tier, one coin/symbols/{symbol}/cohort-metrics?axis=size2026-04-07hourly, every cohortall of it (up to 8,760 hourly buckets: hours)as they were: size_cohort is stored on the row when it is written
Long/short by realized PnL, one coin/symbols/{symbol}/cohort-metrics?axis=pnl&labelBasis=point_in_time2026-04-07hourly, every cohortall of it (up to 8,760 hourly buckets: hours)as they were with labelBasis=point_in_time; daily before 2026-08-06, hourly from it
Smart Money Index, Whale-vs-Shrimp Index, one coin/symbols/{symbol}/indices2026-04-07hourly5,000 hours a page, has_more says there are older onesseries as they were; latest is today's
One cohort across every coin/segments/{cohort}/history?axis=size|pnl2026-04-07hourly5,000 hours a page, has_moreas they were (no labelBasis to pass)
Every open position, every 5 minutes (Parquet)/v1/hyperliquid/archive/url?dataset=positions2026-05-085-minute snapshots, one file a UTC dayone day: 116–123 million rows, 1.6–1.9 GB a daysize_cohort, pnl_cohort, account_value on every row, as of the start of the row's hour
Every fill (Parquet)/v1/hyperliquid/archive/url?dataset=fills2026-05-01every fill, one file a UTC dayone day: 6.8–20.9 million rows a daynone: join a fill to the positions file's labels on wallet and hour, as the large-fills backtest does
Price: candles/v1/hyperliquid/ohlcvthe end of 2025-12-311 minute to 1 day, and tick bars5,000 bars—

Archive sizes: the archive index on 2026-11-04, its seven newest days. Fills, candles and every archive column: Hyperliquid historical data.

Point in time

Labels as they were, or as they are today

Every default and refusal below is the API's own: its validators run when this page is built.

A cohort label is a fact about a wallet at a moment. labelBasis picks the moment: point_in_time is the label the wallet had in that row's own hour, current is today's, resolved when the request runs. grading picks the PnL ladder. The two are independent, which is why every answer says in label_computed when its labels were made.

labelBasis, axis by axis

axisDefaultAsk for the otherThe API answers
sizepoint_in_timelabelBasis=current400: axis='size' is always 'point_in_time': size_cohort is stored on the row at ingestion, so it is already point-in-time. Drop labelBasis, or use axis=pnl for a frozen ladder.
tierpoint_in_timelabelBasis=current400: axis='tier' is always 'point_in_time': the tier bucket is derived from size_usd, which is stored on the row at ingestion, so it is already point-in-time. Drop labelBasis, or use axis=pnl for a frozen ladder.
pnlcurrentlabelBasis=point_in_timeaccepted
pnl_abscurrentlabelBasis=point_in_time400: axis='pnl_abs' is always 'current': pnl_abs is banded from the dictionary's current cumulative realized PnL and was never frozen anywhere, so a point-in-time answer does not exist. Drop labelBasis, or use axis=pnl for a frozen ladder.
pnl_unrealizedpoint_in_timelabelBasis=current400: axis='pnl_unrealized' is always 'point_in_time': the unrealized grading is hour H's rank over the wallet's last snapshot strictly before H, computed by the hourly rollup and stored; a current-graded variant would re-rank history with today's marks — the lookahead the realized axis was rebuilt on 2026-08-17 to remove. Drop labelBasis, or use axis=pnl for a frozen ladder.

grading: which PnL ladder

gradinglabelBasislabel_computed: whenlookaheadresolution
realized the default on axis=pnlcurrentat query timetruenone — one label per wallet, today's
realized use it for every backtestpoint_in_timebefore 2026-08-06, reconstructed from fills on 2026-08-17; from 2026-08-06, frozen on the position row at its own hourfalsedaily before 2026-08-06, hourly after
realized-pit a control, rebuilt dailypoint_in_timereconstructed from fills, day D graded on the cumulative as of the end of D-1falsedaily
unrealized positioning, not skillpoint_in_timeat hour H, over the wallet's last snapshot strictly before Hfalsehourly

Refused, by name

  • /cohort-metrics?axis=size&labelBasis=current → 400: axis='size' is always 'point_in_time': size_cohort is stored on the row at ingestion, so it is already point-in-time. Drop labelBasis, or use axis=pnl for a frozen ladder.
  • /cohort-metrics?axis=pnl&grading=realized-pit&labelBasis=current → 400: grading='realized-pit' is always 'point_in_time': it IS the point-in-time rebuild of the realized ladder, so a current-graded variant of it is exactly grading='realized' with labelBasis=current. Drop labelBasis, or use grading='realized' for a query-time ladder.
  • /cohort-metrics?axis=pnl&grading=unrealized&labelBasis=current → 400: grading='unrealized' is always 'point_in_time': the unrealized grading is hour H's rank over the wallet's last snapshot strictly before H, computed by the hourly rollup and stored; a current-graded variant would re-rank history with today's marks. Drop labelBasis, or use grading='realized' for a query-time ladder.
  • /symbols/{symbol}/metrics?axis=pnl&labelBasis=point_in_time → 400: this endpoint cannot serve labelBasis=point_in_time: its cohort label is resolved from the wallet dictionary at QUERY time, so every historical hour it returns carries today's grading (measured 2026-04-20T12:00Z on BTC's '3x smart': 8.91% more notional than the point-in-time labelling of the same hour). Use /v1/hl-cohorts/symbols/{symbol}/cohort-metrics with axis=pnl&labelBasis=point_in_time, which reads the frozen ladder. ⚠ Before 2026-08-19 this parameter was not declared here and FastAPI silently ignored it, so the same request returned 200 with lookahead rows.

What every answer declares

  • data_from: the first hour the cohort labels are correct; nothing earlier is served (an older start is clamped to it)
  • label_basis: on the envelope AND on every row: point_in_time or current, so rows concatenated from two calls still say how each was labelled
  • grading: which PnL ladder labelled the rows (realized, realized-pit, unrealized), null on axes that rank no PnL
  • label_computed: WHEN the label was computed: when, lookahead (true or false), resolution, source, detail, and a caveat where one applies; on size and tier there is no choice, so no block
  • freshness: for a window that reaches now: newest_ts, behind_hours, stale
  • unavailable_fields: with labelBasis=point_in_time over a window before 2026-08-07: the five position fields that come back null, never 0

The label_computed block of axis=pnl&labelBasis=point_in_time, as the API returns it:

json
{
 "grading": "realized",
 "label_basis": "point_in_time",
 "when": "before 2026-08-06, reconstructed from fills on 2026-08-17; from 2026-08-06, frozen on the position row at its own hour",
 "lookahead": false,
 "resolution": "daily before 2026-08-06, hourly after",
 "source": "hourly cohort snapshots",
 "detail": "Two point-in-time definitions meeting at 2026-08-06. Before it: day D's label is the wallet's rank by cumulative closed PnL as of the END of D-1, reconstructed from fills by the 2026-08-17 rebuild \u2014 so a label is constant across D's 24 hours. From it: the hourly rollup stamps the dictionary value as of that hour onto the row, so the label moves within a day. Neither half can see the future, but a series crossing the seam changes its label RESOLUTION, which a difference or a turnover count will read as a regime change that is an artefact.",
 "caveat": "Rows before 2026-08-06 carry no label frozen at write time: their label is the daily reconstruction described above, not a value stamped on the row at its own hour. From 2026-08-06 the label is frozen on the row and does not change."
}

Routes that state their basis instead of taking one

  • /v1/hl-cohorts/segments/{cohort}/history: takes no labelBasis: as they were on both axes; label_computed on pnl
  • /v1/hl-cohorts/symbols/{symbol}/indices: series as they were; latest is the newest 5-minute snapshot graded today (latest.label_basis: current): keep it out of a backtest
  • /v1/hl-cohorts/symbols/{symbol}/large-fills: pnl_cohort at the fill's own hour where that hour is labelled, else today's, and pnl_cohort_basis says which on every row; size_cohort is today's (size_cohort_basis: current)
  • /v1/hyperliquid/archive/url?dataset=positions: pnl_cohort, size_cohort and account_value on every row, as of the start of the row's hour: no later information
  • /v1/hyperliquid/positions: a historical row's pnl_cohort is its own clock hour's, which counts that hour's later fills (label_computed.lookahead: true); every row says pnl_cohort_basis and pnl_cohort_as_of. label_causal on the same row holds pnl_cohort, pnl_abs_cohort, pnl_cohort_as_of and pnl_cohort_basis as of the hour before, the archive's label (label_computed.causal_field, causal_lookahead: false), null where that hour has no label: use it in a backtest
  • /v1/hyperliquid/hype/holders: pnl_cohort is today's (label_basis: current), and rightly: the holder list has no time axis for a current label to contaminate
7 backtests

Ready backtests, each with its result first

Every code block ran against the live API on 2026-11-04; each result is that run's own output. A weak result is shown as it came out.

HL Cohorts · API

Did cohort positioning predict price? A backtest on labels as they were

Compare the smart cohort's hourly BTC bias with the next hour's return over 30 days, using point-in-time labels so the cohort is the one that existed that hour; then run the same test with today's labels and see the answer change.

Uses
/symbols/{…}/cohort-metrics, /v1/hyperliquid/ohlcv
Verified
live API, 2026-11-04: 5 calls, all 200
Free APIArchiveStreamMCP
Measured 2026-11-04: Same 721 hours of October 2026, same test, two labellings. With each hour's own labels 3x rekt was net long in 421 hours and its bias correlated +0.076 with the next hour's BTC return; with today's labels it was net long in all 721 and the correlation flipped to -0.069. The first script, on 3x smart, finds no edge: that cohort was net short every hour, correlation -0.054.
Ask your AI agent: “With point-in-time labels, did BTC's 3x smart cohort's hourly bias predict the next hour's return in October 2026?”

The code

python
"""Does the smart cohort's BTC positioning say anything about the next hour's return? A point-in-time check.

Run:  VIKE_API_KEY=... python hl-cohort-backtest.py    (--dry-run lists the requests and makes none)
"""
import os
import sys
from datetime import datetime
from statistics import correlation, mean
from urllib.parse import urlencode

NAME = "hl-cohort-backtest"
BASE = "https://data.vike.io"
DRY = "--dry-run" in sys.argv
COHORT = "3x smart"
HOUR_MS = 3_600_000


def fail(why):
    sys.exit(f"{NAME}: {why}")


def call(path, method="GET", **params):
    url = BASE + path + ("?" + urlencode(params) if params else "")
    if DRY:
        print(method, url)
        return None
    key = os.environ.get("VIKE_API_KEY") or fail("set VIKE_API_KEY")
    import requests

    r = requests.request(method, url, headers={"X-API-Key": key}, timeout=60)
    if r.status_code != 200:
        fail(f"{method} {path} answered HTTP {r.status_code}: {r.text[:200]}")
    return r.json()


def main():
    # labelBasis=point_in_time: each row carries the label the wallet had THAT hour. The default
    # resolves labels at query time, which hands a backtest today's grading (lookahead).
    matrix = call(
        "/v1/hl-cohorts/symbols/BTC/cohort-metrics",
        axis="pnl", labelBasis="point_in_time", hours=800,
        start="2026-10-01T00:00:00Z", end="2026-10-31T00:00:00Z",
    )
    candles = call(
        "/v1/hyperliquid/ohlcv",
        symbol="BTC", interval="1h", limit=800,
        start="2026-10-01T00:00:00Z", end="2026-10-31T02:00:00Z",
    )
    if DRY:
        return
    if matrix["label_basis"] != "point_in_time":
        fail("labels are not point-in-time; a backtest on them would look ahead")

    ret = {c["ts"]: c["close"] / c["open"] - 1 for c in candles["candles"]}
    pairs = []  # (bias in hour H, return of hour H+1): positioning is read before the move it is compared to
    for m in matrix["metrics"]:
        if m["cohort"] != COHORT or m["bias"] is None:
            continue
        hour_ms = int(datetime.fromisoformat(m["ts"]).timestamp() * 1000)
        if hour_ms + HOUR_MS in ret:
            pairs.append((m["bias"], ret[hour_ms + HOUR_MS]))
    if len(pairs) < 10:
        fail(f"only {len(pairs)} usable hours for cohort {COHORT!r}")

    long_side = [r for b, r in pairs if b > 0]
    short_side = [r for b, r in pairs if b <= 0]
    print(f"{COHORT}, BTC, {len(pairs)} hours (labels: point_in_time)")
    print(f"  corr(bias, next-hour return) = {correlation([b for b, _ in pairs], [r for _, r in pairs]):+.3f}")
    if long_side:
        print(f"  next-hour return after net-long hours : {mean(long_side) * 1e4:+.1f} bp over {len(long_side)} hours")
    if short_side:
        print(f"  next-hour return after net-short hours: {mean(short_side) * 1e4:+.1f} bp over {len(short_side)} hours")


if __name__ == "__main__":
    try:
        main()
    except Exception as e:  # a shape the example does not expect fails by name, not by traceback
        fail(f"{type(e).__name__}: {e}")
HL Cohorts · API

Fade the losing cohort: mean reversion on extreme bias

Pull a coin's hourly realized-PnL matrix with labelBasis=point_in_time, take the 3x rekt rows' bias, and test whether its extremes are followed by a move the other way in /ohlcv.

Uses
/symbols/{…}/cohort-metrics, /v1/hyperliquid/ohlcv
Verified
live API, 2026-11-04: 4 calls, all 200
Free APIArchiveStreamMCP
Measured 2026-11-04: No edge in fading. When BTC's 3x rekt bias was 2 standard deviations above its trailing week (34 events, 2026-08-14 to 11-03), BTC rose +11.1 bp over the next 6 hours on average and fading was right 11 of 34 times; after the most-short extremes (21) it rose +34.5 bp and fading was right 12 of 21. Every hour: +6.4 bp.
Ask your AI agent: “Show the hourly bias of BTC's 3x rekt cohort for the week of 2026-10-13 with point-in-time labels”

The code

python
import os
import requests

API_KEY = os.environ["VIKE_API_KEY"]  # your key, read from the environment

# BTC's hourly pnl-cohort matrix over one week, labels as they were each hour
r = requests.get(
    "https://data.vike.io/v1/hl-cohorts/symbols/BTC/cohort-metrics?axis=pnl&labelBasis=point_in_time&start=2026-10-13T00:00:00Z&end=2026-10-19T23:59:59Z&hours=168",
    headers={"X-API-Key": API_KEY},
)
HL Cohorts · API

Smart Money Index versus price: does it lead?

Pull a coin's hourly Smart Money Index from /indices for a pinned window (one page holds up to 5,000 hours) and its hourly candles from /ohlcv, then correlate SMI at hour t with the return over the next k hours.

Uses
/symbols/{…}/indices, /v1/hyperliquid/ohlcv
Verified
live API, 2026-11-04: 4 calls, all 200
Free APIArchiveStreamMCP
Measured 2026-11-04: Over 2,136 hours (2026-08-07 to 11-03) BTC's SMI did not lead price up: its correlation with the next 24 hours' return was -0.209 (-0.041 at 1 hour). After the top fifth of SMI readings BTC moved -28 bp over 24 hours, after the bottom fifth +104 bp. Overlapping 24-hour returns in one 89-day window: a lean, not a proof.
Ask your AI agent: “Give me BTC's hourly Smart Money Index over the last 30 days and BTC's hourly closes for the same hours”

The code

python
import os
import requests

API_KEY = os.environ["VIKE_API_KEY"]  # your key, read from the environment

# BTC's hourly Smart Money Index over 30 days, one page
r = requests.get(
    "https://data.vike.io/v1/hl-cohorts/symbols/BTC/indices?start=2026-10-05T00:00:00Z&end=2026-11-03T23:59:59Z",
    headers={"X-API-Key": API_KEY},
)
HL Cohorts · API

Are Whale-vs-Shrimp extremes a contrarian signal?

Take the hourly wsi series from /indices, z-score it against its own trailing month, and check whether the 24-hour return from /ohlcv after an extreme goes against the side the index leaned.

Uses
/symbols/{…}/indices, /v1/hyperliquid/ohlcv
Verified
live API, 2026-11-04: 5 calls, all 200
Free APIArchiveStreamMCP
Measured 2026-11-04: A coin flip on ETH. 27 extremes of the Whale-vs-Shrimp Index (|z| > 2 against its trailing 30 days, 1,400 hours): after whales were most long the price went against them 6 of 12 times (-7 bp over 24 hours), after they were most short 9 of 15 (-32 bp).
Ask your AI agent: “Give me ETH's hourly Whale-vs-Shrimp Index over the last 90 days and the hours where it was more than two standard deviations from its 30-day mean”

The code

python
import os
import requests

API_KEY = os.environ["VIKE_API_KEY"]  # your key, read from the environment

# ETH's hourly indices over four hours of 2026-10-20
r = requests.get(
    "https://data.vike.io/v1/hl-cohorts/symbols/ETH/indices?start=2026-10-20T00:00:00Z&end=2026-10-20T03:00:00Z",
    headers={"X-API-Key": API_KEY},
)
HL Cohorts · API

Hyperliquid net flow by cohort, hour by hour

Difference each cohort's long and short size from one cohort-metrics call; an hour's flow is the change in long minus the change in short, in tokens of the coin, positive when the cohort is buying.

Uses
/symbols/{…}/cohort-metrics, /v1/hyperliquid/ohlcv
Verified
live API, 2026-11-04: 5 calls, all 200
Free APIArchiveStreamMCP
Measured 2026-11-04: Over 743 hours (2026-10-04 to 11-03) BTC shrimp sold into rising hours: the correlation of their hourly net flow with the same hour's return was -0.426; whales' was +0.017. Neither group's flow predicted the next hour (shrimp -0.060, whales -0.046).
Ask your AI agent: “For BTC, take each size cohort's long and short position size for the last 25 hourly buckets and compute its 24 hourly net flows in BTC: the change in long minus the change in short”

The code

python
import os
import requests

API_KEY = os.environ["VIKE_API_KEY"]  # your key, read from the environment

# BTC open position size by account-value cohort, 25 hourly buckets
r = requests.get(
    "https://data.vike.io/v1/hl-cohorts/symbols/BTC/cohort-metrics?axis=size&hours=25",
    headers={"X-API-Key": API_KEY},
)
HL Cohorts · API

Backtest following smart money with point-in-time cohort labels from the positions archive

Download a day of the positions archive; every 5-minute position row carries pnl_cohort, size_cohort and account_value as they stood at the start of its hour, so a backtest never reads a label from the future.

Uses
/v1/hyperliquid/archive/datasets, /v1/hyperliquid/archive/url, /v1/hyperliquid/ohlcv, /v1/hyperliquid/positions
Verified
live API, 2026-11-04: 11 calls, all 200
Free APIArchiveStreamMCP
Measured 2026-11-04: Seven positions files (2026-10-28 to 11-03, 1,997 snapshots), BTC rows only: the three smart cohorts were net short throughout, from -2,499 BTC to -3,793 BTC. The hour's change in their net position correlated -0.157 with the next hour's return over 167 hours: weak, and a week is a small sample.
Ask your AI agent: “Which BTC positions did 3x smart wallets hold in the 12:00 UTC hour of 2026-10-22, with the label they had then?”

The code

python
import os
import requests

API_KEY = os.environ["VIKE_API_KEY"]  # your key, read from the environment

# a 7-day link to every 5-minute position snapshot of 2026-10-23
r = requests.get(
    "https://data.vike.io/v1/hyperliquid/archive/url?dataset=positions&date=2026-10-23",
    headers={"X-API-Key": API_KEY},
)
HL Cohorts · API

Do smart-money wallets' large fills precede price moves?

/large-fills holds a coin's newest 200 fills over $50,000, so live you log it on a timer; for past days, label the fills archive with the positions archive's point-in-time pnl_cohort and measure the price 10 minutes after each smart-money taker fill with 1-minute /ohlcv.

Uses
/symbols/{…}/large-fills, /v1/hyperliquid/archive/url, /v1/hyperliquid/ohlcv
Verified
live API, 2026-11-04: 13 calls, all 200
Free APIArchiveStreamMCP
Measured 2026-11-04: Not a signal. 25,674 BTC taker fills of $50,000 or more on 2026-11-01 to 11-03, 98% labelled from the positions files: the 12,189 by smart wallets saw the price move their way +0.10 bp 10 minutes later, 48% of the time; all of them -0.40 bp, 47%.
Ask your AI agent: “Show the latest BTC fills over $50,000 where a 3x smart wallet crossed the book, and the BTC price ten minutes after each”

The code

python
import os
import requests

API_KEY = os.environ["VIKE_API_KEY"]  # your key, read from the environment

# every BTC fill over $50,000 the feed holds; run it every few minutes and append the new rows to your log
r = requests.get(
    "https://data.vike.io/v1/hl-cohorts/symbols/BTC/large-fills",
    headers={"X-API-Key": API_KEY},
)
Parquet

Download every position with its labels

One file per UTC day from 2026-05-08: every open position at every 5-minute snapshot, three point-in-time columns on every row.

ColumnWhat it holds
size_cohortAccount-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.
pnl_cohortRealized-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.
account_valueNative 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.
python
import os, requests
import polars as pl

link = requests.get(
    "https://data.vike.io/v1/hyperliquid/archive/url",
    params={"dataset": "positions", "date": "2026-11-03"},
    headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
    timeout=30,
).json()["url"]                      # needs no key, works 7 days
# polars reads only the byte ranges it needs: BTC's rows of a 1.6-1.9 GB file
book = (pl.scan_parquet(link)
        .filter(pl.col("asset") == "BTC")
        .group_by("snapshot_ts", "pnl_cohort")
        .agg((pl.col("direction") * pl.col("size_usd")).sum().alias("net_usd"))
        .sort("snapshot_ts")
        .collect())
print(book.filter(pl.col("pnl_cohort").is_in(["3x smart", "2x smart", "smart"])).tail(6))
  • The link needs no key and works 7 days; asking for it is one request, the download itself spends none.
  • 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 %.
  • The fills files (from 2026-05-01) carry no cohort: the “Label the fills” tab joins them to the positions file's labels on wallet and hour.
  • Free samples, no key: fills-sample.parquet · positions-sample.parquet · manifest.json.
  • Every column of both files, the fills download and the candles: Hyperliquid historical data.
FAQ

Hyperliquid backtest FAQ

What goes wrong if a backtest uses today's labels?
The cohort a wallet is in changes as it trades. Graded today, the hours of last month count the wallets that went on to win among the winners, so the backtest already knows who won. On BTC, over the 720 hours to 2026-11-04, 3x rekt sat on the other side of the market in 32.2% of hours under today's labels. Pass labelBasis=point_in_time, and check label_computed.lookahead: false in the answer.
How far back does Hyperliquid cohort data go?
Cohort data from 7 April 2026: the first hour the labels are correct. The positions files start on 8 May 2026, the fills files on 1 May 2026. One request returns all of it; the examples here use 30 to 90 days.
Why do the labels start on 7 April 2026?
The hourly point-in-time labels start at 2026-04-07 00:00 UTC; the days just before it are incomplete (2026-04-03 holds 20 of its 24 hours while the tracked book grows from $2.9B to $6.6B), so the cohort routes start there. Until 2026-08-06 a day's labels are rebuilt from fills (graded on each wallet's PnL as of the end of the day before); from it they are frozen hour by hour on the position rows.
What is the difference between realized and unrealized grading?
grading=realized ranks wallets by cumulative closed PnL (at least 10 closed trades, else unknown): who has been good. grading=unrealized ranks them by the unrealized PnL of their open positions as a share of account value: who is onside now. It turns over about 44% of wallets a day against about 2.4% for the realized ladder, and part of it restates the move that just happened, so read it as positioning, not skill.
Are Hyperliquid fills labelled with a cohort?
No: the fills file has no cohort column. The positions file does, on every row, as of the start of the row's hour. Join a fill to it on wallet and the fill's hour, as the large-fills backtest above does: 98% of BTC taker fills of $50,000 or more found a label on 2026-11-01 to 11-03.
Can I run these backtests from Claude or ChatGPT?
Yes. hl_cohort_metrics with label_basis=point_in_time answers the cohort questions, hl_market view=indices the indices, hl_ohlcv the candles and hl_archive the download links, on one free key.