vike
GuidesHyperliquid

Find top traders and read their record

Rank Hyperliquid traders by 30-day realized PnL, then pull the best one's trade statistics, with the median beside the mean.

Two calls. /traders ranks the venue; the first row's address goes to /wallets/{address}/trades/summary, which returns that wallet's statistics overall and per coin.

Run
export VIKE_API_KEY=...
python hl-top-traders.py             # real run: needs the key
python hl-top-traders.py --dry-run   # prints the requests it would make, calls nothing

The script prints the three leaders, then the winner's trade count, net PnL and the mean and median of its gains and losses. A wallet with no trades in the window ends the run with a message naming the recipe.

hl-top-traders.py
"""Rank Hyperliquid traders by 30-day realized PnL, then look inside the best one's record.

Run:  VIKE_API_KEY=... python hl-top-traders.py        (--dry-run lists the requests and makes none)
"""
import os
import sys
from urllib.parse import urlencode

NAME = "hl-top-traders"
BASE = "https://data.vike.io"
DRY = "--dry-run" in sys.argv


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():
    # /traders returns one page of at most 20 and has no cursor; three is plenty here.
    board = call("/v1/hyperliquid/traders", window="30d", sort="pnl", size=3)
    wallet = "0x25773676902085225ae5751075e4161369334897"  # --dry-run has no response to read
    if not DRY:
        if not board["traders"]:
            fail("/traders returned no traders")
        for t in board["traders"]:
            print(f"{t['address']}  pnl ${t['total_realized_pnl']:,.0f}  win rate {t['win_rate_pct']}%  trades {t['trade_count']}")
        wallet = board["traders"][0]["address"]

    summary = call(f"/v1/hyperliquid/wallets/{wallet}/trades/summary", window="30d")
    if DRY:
        return
    o = summary["overall"]
    if not o["trade_count"]:
        fail(f"{wallet} has no trades in the window")
    # One outsized trade drags a mean far from the typical trade, so read the median beside it.
    print(f"{wallet}: {o['trade_count']} trades, net ${o['net_pnl']:,.0f}")
    print(f"  gain   mean ${o['avg_gain']:,.2f}  median ${o['median_gain']:,.2f}")
    print(f"  loss   mean ${o['avg_loss']:,.2f}  median ${o['median_loss']:,.2f}")


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

Read it this way

  • Mean against median. The summary carries both because one outsized trade drags a mean far from anything the wallet typically does. If avg_gain is many times median_gain, a few trades made the record.
  • Pass the window to both calls. Both take 1d, 7d, 30d or all, but their defaults differ: /traders ranks over 30d when you send nothing and the summary covers all. Leave the second window out and the leaderboard's 30 days become the wallet's whole history.
  • Even with the same window the two counts differ. /traders counts completed trades by the day they opened; the summary also counts open trades and trades opened before the window, so its trade_count and net_pnl run higher. The /traders page reconciles one wallet.

Three behaviours of /traders that a script trips over

size is clamped to 20 whatever you send, there is no cursor, and an unrecognised sort silently falls back to the PnL ranking rather than failing. For a deeper board, with a cursor and a 400 on a typo, use /leaderboard.

On this page