Repository navigation
hyperliquid publishes a leaderboard whose own fields contradict each other - #2793
Merged
Merged
Conversation
…other The venue serves its trader table as one public blob: 47,123 accounts with PnL, ROI, volume and equity, no key. We do not copy it. Copying it would add nothing, and reading it turns up three things its UI cannot show. `pnl` and `vlm` sit in one row and count different universes. Volume is perp notional; PnL also absorbs spot, vaults and staking. 2,904 accounts publish a non-zero lifetime PnL against exactly zero lifetime volume, worth $5.3bn, 32% of the aggregate. The aggregate cannot be what it says: +$16.54bn across the set with 51.3% of accounts in profit. Perp PnL is zero-sum net of fees, so a positive sum over a majority of winners is arithmetic proof the set is not the population. Spot and HLP yield genuinely are not zero-sum, which is the same finding from the other side. `roi` ships without its denominator. 590 accounts publish above 10,000%, the highest past 2,600,000% on five figures of volume. The info API's `portfolio` request settles the first point per account rather than by inference: it mirrors every window perp-only, so the split is read, not modelled. It also already carries ~90 points of history back to first deposit, so rank persistence needs no waiting period. No node is involved. The public CDN serves builder_fills and nothing else (node_fills, fills, node_trades, trades, asset_ctxs, market_data, liquidations, funding all 403), and per-account PnL does not need fills. Guards worth naming. An unreadable volume is not a zero volume: a lenient parse would have turned any future format change into thousands of fabricated members of the headline cohort, so malformed rows are counted and published instead. Sampling takes the highest-PnL accounts, which biases the perp split toward the zero-volume cohort by construction (81.7% on the top 8 against 18% mid-table), so the sample size travels with the figure everywhere it is published. /hyperliquid/traders also had to be excluded from the hub's slug consolidation, which 308s everything under /hyperliquid to /products/<slug>#hl and was sending the new page to a 404. The hub's own share card still credited the node retired on 2026-09-23; corrected. The page renders an explicit "awaiting the first run" state until the collector has published once, because zeros here would read as a measurement.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hyperliquid serves its trader table as one public blob: 47,123 accounts with PnL, ROI, volume and equity, keyless, ~39 MB. This does not copy it. Copying it would add nothing, because the venue already renders it. Reading it turns up three things its UI cannot show.
What the blob says
pnlandvlmcount different universes, in the same row. Volume is perp notional; PnL also absorbs spot, vault deposits (HLP) and staking. 2,904 accounts publish a non-zero lifetime PnL against exactly zero lifetime volume, worth $5.3 bn, 32 % of the leaderboard's aggregate. The table sorts on both fields.The aggregate cannot be what it says. The rows sum to +$16.54 bn with 51.3 % of accounts in profit. Perp PnL is zero-sum between longs and shorts net of fees, so a positive aggregate over a majority of winners is arithmetic proof the published set is not the population: the losers are absent. Spot and HLP yield genuinely are not zero-sum, which is the same finding from the other side. three P&L universes summed into one sortable column.
roiships without its denominator. 590 accounts publish an ROI above 10,000 % in absolute value, the highest past 2,600,000 % on five figures of volume.Why no node
Per-fill data for every account needs a Hyperliquid node. The public CDN serves
builder_fillsand nothing else.node_fills,fills,node_trades,trades,asset_ctxs,market_data,liquidationsandfundingall answer 403. The SGP node stalled on 2026-09-23 and only the requester-pays S3 snapshot would bootstrap it.It is also unnecessary. The info API's
portfoliorequest publishes per-account PnL directly with a perp-only mirror of every window, so the split is read rather than inferred, and it already carries ~90 points of history back to first deposit. Rank persistence therefore needs no waiting period, which is the opposite of what we assumed going in.Two guards worth reviewing closely
An unreadable volume is not a zero volume. The zero-volume cohort is the headline and is defined as "volume zero, PnL not". A lenient parse would turn any future format change into thousands of fabricated members of that cohort, silently. Rows whose lifetime window does not read are now counted into
hl_traders_malformed_rowsand surfaced on the page instead. The live blob had zero non-numeric fields across all 47,123 rows on 2026-10-03, so this guards tomorrow, not today.TestUnreadableVolumeIsNotZeroVolumepins it.The perp-split sample is biased on purpose, and says so.
HL_TRADERS_SAMPLE_SIZEreads the highest-PnL accounts, because those are the rows the leaderboard showcases. That selects for the zero-volume cohort by construction: the top 8 came back 81.7 % non-perp against 18 % on a single mid-table account.hl_traders_sampled_accountsis published alongside the percentage and the page states "of the top N" rather than implying a population figure.Two things found while wiring it
/hyperliquid/traderswas 308ing to/products/traders#hl. The hub's slug consolidation (next.config.ts) redirects everything under/hyperliquid/except the image routes, so the new page landed on a 404. Added to the exclusion, with a note that future hub sub-pages need the same.Shape
Second collector in the existing
hl-archivebinary, stateless, independent of the builder-fills pipeline. Gauges are set from the leaderboard audit before the info API sweep starts, so a rate-limited sweep still leaves the leaderboard figures published. Its own Upstash key, capped at 500 rows (free-tier ceiling is 1 MB/key, the blob is ~39 MB); the audit figures are aggregates over all ~47 k regardless. New gauges ride the existing/metrics, so no Prometheus scrape change is needed.The page renders an explicit "awaiting the first run" state until the collector has published once, because zeros here would read as a measurement.
Verified
go build,go vet,gofmtclean; 9 new tests pass, full Go suite otherwise unchangedpnpm validate235 specs,pnpm typecheck,pnpm test425 pass / 0 fail, eslint clean on every touched file/hyperliquid/traders: one h1, canonical,index, follow, title 56 ch, description 159 ch, share card generated (1200×630 PNG)Not in this PR
TestRunCatchup_GapFillfails on this branch and on cleandev— a date-drifting expectation in the 120-day boot-refill window, pre-existing and untouched here.Deploy note
The collector must run once on the VPS (
hl-archive traders) before the page shows anything. Until then it states that plainly.