On Hyperliquid, an onchain crypto exchange, every account's positions and fills are public. That makes a whale tracker possible: rank the largest positions in a market, separate the market makers from wallets taking a view, read what those wallets did, and get a signed alert when one trades. This guide builds each step, then sizes a copy of a wallet's positions without placing an order. Every request ran on 8 November 2026 between 16:02 and 16:06 UTC, and every output is what came back.
Short answer
- Find the whales:
GET /v1/hyperliquid/positions/{symbol}?min_value=10000000counted all 47 BTC positions worth $10 million or more in one request and returned the largest 10: 22 longs holding $590 million and 25 shorts holding $994 million. Follow the cursor for the rest. Hyperliquid's own API only answers for an address you already know. Then profile each address, read its positions and fills, and alert on the ones worth following. - Tell makers from traders: 7 of the 20 largest BTC holders made over 5,000 BTC fills in a week, but only 3 of them made 70% or more of their fills as makers. The wallet profile route returns maker share and orders per fill for each.
- Read a wallet's trades: the change log returned 2,363 fills for one wallet's last 24 hours in one request, each with the position before and after. All 1,780 fills in a 6-hour test matched Hyperliquid's own API trade for trade.
- Get alerts: signed webhooks on watched wallets, from the Build plan. Run the estimate first: a $25,000 fill alert on three busy wallets would have fired 17,394 times in a week.
Why build this on Hyperliquid
On most exchanges other traders' positions are private, and you see an aggregate ratio at most. On Hyperliquid each position carries the account that holds it, so you can rank holders, read one account's full trade history and watch it in near real time. An address identifies an account, not a person, and one person can run many accounts.
What you need
- Python 3 with
requests. The snippets are short prototypes with no retry logic. - A 0xArchive API key in the
OXARCHIVE_API_KEYenvironment variable. Free keeps a rolling 30 days of history. See pricing for the plan that fits your volume. Webhook delivery starts on the Build plan. - No key for the native Hyperliquid calls, which go to
https://api.hyperliquid.xyz/info.
Step 1: find the big wallets in a market
Hyperliquid's own API has no call that lists who holds what in a market. You can only ask about an address you already know. The 0xArchive market positions route returns every open position in one market, largest first, with totals over the whole filtered set. Here it is for BTC, keeping positions worth at least $10 million:
import os, requests
H = {"X-API-Key": os.environ["OXARCHIVE_API_KEY"]}
B = "https://api.0xarchive.io/v1/hyperliquid"
r = requests.get(f"{B}/positions/BTC", headers=H,
params={"min_value": 10_000_000, "limit": 10})
j = r.json()
t = j["meta"]["totals"]
print(j["meta"]["as_of"], t["long_count"], t["long_value"], t["short_count"], t["short_value"])
for p in j["data"][:4]:
print(p["user_address"][:6] + ".." + p["user_address"][-4:], p["side"], p["size"], p["entry_price"], p["position_value"], p["unrealized_pnl"])2026-11-08T15:58:34.000Z 22 590072687.83855 25 994042558.4418
0x5b5d..c060 short -2354.59675 76390.2 179078855.82125 789267.068082
0xb83d..6e36 short -2090.39212 77925.8 158984772.6866 3910740.426916
0xfc27..9d9d short -1336.0258 78645.4 101611442.219 3460964.471982
0x8ea8..edbc long 1000 72771.2 76055000 3283721.68354One request, 0.17 seconds. At 15:58 UTC there were 47 BTC positions worth $10 million or more: 22 longs holding $590.1 million and 25 shorts holding $994.0 million. Each row carries the wallet, side, size, entry price, mark price, value and unrealized PnL. The snapshot refreshes every five minutes, so meta.as_of tells you how old it is. Isolated positions also show a liquidation_price. Cross-margin positions show null, because the price depends on the rest of the account.
The same route takes side, hour (a past hourly snapshot) and a cursor for paging. The account positions reference lists every parameter.
Step 2: decide which wallets are worth following
Hyperliquid's own leaderboard is one place to start: on 9 November 2026 it ranked 48,247 accounts by PnL, return and volume over a day, week, month and all time. It does not say what an account holds in a given market today, or how it trades. Size alone says little too. Many of the biggest wallets are market makers, and their position moves because they are quoting both sides, not because they have a view. The wallet profile route scores behavior over the last one to 168 hours, so you can filter them out. We pulled the profile of the 20 largest BTC holders with coin=BTC and hours=168 (20 requests, 9.1 seconds):
top = requests.get(f"{B}/positions/BTC", headers=H, params={"limit": 20}, timeout=30).json()["data"]
for p in top:
a = p["user_address"]
m = requests.get(f"{B}/wallets/{a}/profile", headers=H, timeout=30,
params={"coin": "BTC", "hours": 168}).json()["data"]["metrics"]
print(a[:6] + ".." + a[-4:], p["side"], round(float(p["position_value"]) / 1e6, 1),
m["total_fills"], m["maker_ratio"], m["order_to_trade_ratio"])The output, as a table (6 of the 20 rows):
| Wallet | Side | BTC value | BTC fills, 7 days | Maker share | Orders per fill |
|---|---|---|---|---|---|
| 0x5b5d..c060 | short | $179.1M | 5,049 | 27.1% | 3.0 |
| 0xfc27..9d9d | short | $101.6M | 10,637 | 72.2% | 355.4 |
| 0x8ea8..edbc | long | $76.1M | 0 | n/a | n/a |
| 0x3ff5..f705 | short | $69.1M | 10,027 | 84.1% | 1,194.2 |
| 0xf081..e39e | long | $40.4M | 7,104 | 0.0% | 0.0 |
| 0x7fda..17d1 | short | $25.3M | 16,195 | 100.0% | 17.2 |
Seven of the 20 had more than 5,000 BTC fills in seven days. A wallet with a 70 to 100 percent maker share is quoting the book: three of the seven, one of them at 1,194 orders per fill. Fill count alone does not make a market maker. A wallet with zero BTC fills in seven days is holding a position it opened earlier. The wallet 0xf081..e39e is different: 7,104 fills, none of them as a maker, and a growing long. That is a taker building a position. We follow it from here on.
The profile also reports cancel rate, median cancel speed, TWAP use, realized PnL, liquidation count and fees. Without coin, it counts every UTC day that overlaps the window in full. With coin, the window is exact.
Step 3: read a wallet's positions and what changed
Two routes cover one wallet. /wallets/{address}/positions returns open positions and an account summary now, or at any past instant with timestamp. /wallets/{address}/positions/changes returns every fill leg with the position before and after, in time order.
import time
W = "0xf081..." # the full 42-character address
snap = requests.get(f"{B}/wallets/{W}/positions", headers=H, timeout=30).json()
print(snap["data"]["account"]["account_value"], snap["data"]["account"]["n_positions"])
for p in snap["data"]["positions"]:
print(p["symbol"], p["side"], p["size"], p["position_value"], p["entry_price"])
end = int(time.time() * 1000)
chg = requests.get(f"{B}/wallets/{W}/positions/changes", headers=H,
params={"start": end - 24*3600*1000, "end": end, "limit": 5000}).json()
print(len(chg["data"]), chg["meta"]["built_through"])4328066.633906 3
BTC long 530.86129 40374655.41095 77820.1
ZEC long 7554.81 9611984.763 1268.7386
HYPE long 104899.19 8118473.501589 84.2797
2363 2026-11-08T15:00:00.000ZThe wallet holds three longs on $4.33 million of account value, so its positions total $58.1 million, about 13 times account value. The change log returned 2,363 rows for the last 24 hours in one request that took 0.24 seconds: 2,079 increases, 283 reductions and 1 open. Each row has event_type (open, increase, reduce, close or flip), start_position, end_position, entry_price_after, closed_pnl, fee, opened_at and the trade ID. The wallet traded $13.0 million of BTC and $9.6 million of ZEC in that window.
Two timing details matter. The change log is capped at meta.built_through, which was 15:00 UTC when we asked at 16:03. Rows after that are not served yet, so this route is for review and backfill, not for chasing the last minute. Current position snapshots refresh every five minutes.
Step 4: get an alert when the wallet trades
You have two options. Poll Hyperliquid's clearinghouseState yourself, or let 0xArchive push signed webhooks for the wallets you register.
Option A: poll the native API
Hyperliquid limits REST requests to a combined weight of 1,200 per minute per IP address. A clearinghouseState call has weight 2, so one IP can make at most 600 per minute. We timed 50 in a row: 10.6 seconds, about 211 ms each, no errors.
import requests
N = "https://api.hyperliquid.xyz/info"
state = requests.post(N, json={"type": "clearinghouseState", "user": W}).json()
for ap in state["assetPositions"]:
print(ap["position"]["coin"], ap["position"]["szi"])Polling gives you a position, not a trade. To see each fill you diff successive states, or call userFillsByTime, which costs weight 20 plus extra weight for every 20 rows returned. For 24 hours of this wallet's fills we needed 2 requests and got 2,334 rows. A busy wallet burns the budget quickly. The native WebSocket is free of that weight, but Hyperliquid caps user-specific subscriptions at 10 unique users per IP.
Option B: webhooks for watched addresses
0xArchive Webhook Alerts send a signed HTTPS POST when a registered wallet does something. You add the wallet to a watched list, then create a rule on an account.* event type. We read the live event catalog. The ones for wallets are:
account.fill: a watched wallet executed in a market. Fills in the same market, block and account are collapsed into one event with summed notional. Declared latency class: seconds.account.liquidated: a watched wallet was liquidated. Seconds.account.twap_lifecycle: a TWAP changed state. Minutes.account.transfer: a spot token moved in or out. Seconds.account.order_rejected: the engine cancelled an order the account did not cancel. Minutes.
Rules can filter by venue, symbols, addresses and conditions such as notional_usd, side, taker or notional_pct_wallet_volume_30d. Plans set the allowances: Build has 2 watched wallets and 5,000 deliveries a day, Pro has 15 and 50,000, Scale has 50 and 500,000. Free has no webhook delivery. Our account is on Pro, and GET /v1/webhooks/limits reported 15 watched wallets and 50,000 deliveries a day, which matches the docs.
To switch an alert on: add the wallet to your watched addresses, create an account.fill rule with your venue, symbol and size filters, and check 0xa-signature on every delivery before you act on it. We did not create a rule for this guide. We did the sizing step instead, which creates nothing and answers the question that decides whether a rule is usable: how often would it fire. The estimate replays the rule over the last seven days of the wallets already on our watched list:
body = {"event_type": "account.fill",
"config": {"venue": "hyperliquid",
"conditions": [{"metric": "notional_usd", "op": "greater_than_or_equal", "value": 25000}]},
"lookback_days": 7}
d = requests.post("https://api.0xarchive.io/v1/webhooks/subscriptions/estimate", headers=H, json=body).json()["data"]
print(d["total"], d["per_day_p50"], d["per_day_max"])
for step in d["ladder"][-4:]: print(step["value"], step["per_day"])17394 2204.0 4877
238000.0 579.0
907000.0 101.0
3450000.0 7.86
13099999.999999998 0.14At a $25,000 threshold, the three wallets on our watched list would have produced 17,394 events in seven days, with a busiest day of 4,877. That is too many to read. The ladder shows the fix: at $3.45 million the same wallets produce about 8 events a day. Always run the estimate before you create a rule, and pick the threshold from the ladder. Delivery counts against your daily allowance, and a rule that crosses it is paused.
When the rule exists, your endpoint receives a POST with an 0xa-signature header. Check it against the raw body before you trust the event. This is the check from the signatures reference, run here on a synthetic delivery:
import hmac, hashlib, time
def verify_webhook(secret, header, raw, tol=300):
t, sigs = None, []
for part in header.split(","):
k, _, v = part.partition("=")
if k.strip() == "t": t = v.strip()
elif k.strip() == "v1": sigs.append(v.strip())
if not t or not sigs or abs(time.time() - int(t)) > tol:
return False
exp = hmac.new(secret.encode(), t.encode() + b"." + raw, hashlib.sha256).hexdigest()
return any(hmac.compare_digest(exp, s) for s in sigs)signature check good: True | tampered body: FalseRecord the event by its id before you answer with a 2xx. Delivery is at least once, so the same event can arrive twice.
Copy trading: size a follow without placing orders
You can follow a wallet two ways. Mirror each fill, or mirror the position. Mirroring fills is a poor fit for this wallet: it made 2,363 fill legs in a day, many of them under 2 ZEC. Fees and rounding on your side would eat the edge. Mirroring the position is simpler. Read the leader's open positions, scale them to your equity, and trade the difference.
The scale is your equity divided by the leader's account value. With $50,000 of equity and the leader's $4.33 million, the scale is 0.01155. The function below returns target sizes and places no orders:
def target_positions(wallet, my_equity):
j = requests.get(f"{B}/wallets/{wallet}/positions", headers=H).json()
scale = my_equity / float(j["data"]["account"]["account_value"])
return {p["symbol"]: float(p["size"]) * scale for p in j["data"]["positions"]}, scale
tgt, scale = target_positions(W, my_equity=50_000)
for sym, t in tgt.items():
print(f"{sym:5} target {t:12.4f}")BTC target 6.1328
HYPE target 1211.8481
ZEC target 87.2770Use the change log to understand how the leader builds a position before you copy it: event_type shows whether a trade opened, added, trimmed or flipped, and closed_pnl shows what each reduction earned. Use the webhook to know when to re-read the position. Check three things before you act on a target:
- The snapshot is up to five minutes old, so you copy a position the leader may have already changed.
- Scaling by account value copies the leader's leverage. This leader holds about 13 times its account value, and so would you. Cap leverage in your own code, and round each target to the market's
szDecimalsbefore you order. - The leader's entry is not your entry. Compare
entry_pricewith the current mark before you chase a position that is already up 10 percent.
Check the leader's results before you copy them. This wallet's 283 reductions in the 24-hour log closed at a combined closed_pnl of -$332,424, with $6,314 in fees. The profile route reports realized_pnl_usd, total_fees_usd and liquidation_count over the last 168 hours.
Side by side: Hyperliquid's API and 0xArchive
We ran each task against both on November 8, 2026.
| Task | Hyperliquid native API | 0xArchive |
|---|---|---|
| List the largest positions in BTC | No call for it. You need the addresses first. | 1 request, 0.17 s: 47 positions of $10M or more, with totals. |
| One wallet's positions now | clearinghouseState, weight 2, about 0.2 s. | /wallets/{address}/positions, 0.18 s, with account summary. |
| One wallet's last 24 hours of fills | 2 requests, 1.18 s, 2,334 rows. | 1 request, 0.24 s, 2,363 rows with position before and after. |
| Same 6 hour window, trade IDs compared | 1,780 fills | 1,780 rows. All 1,780 trade IDs match. None missing on either side. |
| Fills on September 1, 2025 for a busy wallet (0x7fda..17d1) | 0 rows returned | 5,000 rows (the page limit) |
| Wallet behavior score | Not offered. Compute it from fills. | /wallets/{address}/profile |
| Alerts | Poll (600 clearinghouseState calls a minute per IP) or WebSocket (10 users per IP). | Signed webhooks for 2 to 50 watched wallets by plan. |
The native API is enough when you track a handful of known wallets for the last few days. The fills match ours exactly. It falls short for discovery, for history, and for volume. Hyperliquid documents that userFillsByTime returns at most 2,000 fills per response and only the 10,000 most recent fills. For the wallet we used, a request starting in May 2025 returned fills beginning October 16, 2026. 0xArchive's change log starts May 25, 2025 15:00 UTC and was non-empty for September 1, 2025 on the busy wallet above. For the full comparison, see Hyperliquid's native API vs 0xArchive.
What to show and how to read it
- Net side and size of the large holders. In BTC, 25 shorts of $10 million or more held $994 million against $590 million in 22 longs. Show the ratio and the top ten share, which the market summary route also returns.
- Maker share, fill count and PnL next to every wallet. Size alone makes a market maker look like the most convinced trader in the market, and fill count alone makes a busy taker look like a market maker.
- Unrealized PnL and entry price. It shows how far each holder sits from its entry. In the BTC list, the long at $83,285 was down $6.6 million while the market was at $76,055.
- Position change, not position. A $76 million long that has not traded in seven days (the 0x8ea8..edbc row) says less than a $40 million long held by a wallet that added to its positions 2,079 times in a day.
What to know before you build
- Snapshots are periodic. Current positions refresh every five minutes. Check
meta.as_ofandmeta.stale. - The change log trails the clock. At 16:03 UTC it was served through 15:00 UTC. Use webhooks for anything recent.
- Query the account that holds the position. An agent or API wallet that signs for an account returns an empty result.
- HIP-3 is a separate route. One wallet had 263 native fills on October 1, all in
xyz:BRENTOIL. The core route returned 0 rows and the HIP-3 route/v1/hyperliquid/hip3/wallets/{address}/positions/changesreturned 263. - Empty is not zero. Before May 25, 2025 15:00 UTC the change log returns an empty page with
meta.coverage_from. - Webhooks watch only wallets you register. They cannot alert on "any wallet over $10 million." Run Step 1 on a schedule, add new wallets, and remove stale ones within your plan's limit.
- Addresses are accounts. Do not publish addresses next to claims about who owns them.
FAQ
How do I find whale wallets on Hyperliquid?
Read the market positions route for each market you care about and set min_value. Hyperliquid's own API only answers for an address you already have.
Can I get an alert when a specific wallet trades?
Yes. Add the address to your watched list and create an account.fill rule. It needs the Build plan or above. The native alternative is to poll clearinghouseState or subscribe over WebSocket for up to 10 users.
How far back can I see one wallet's trades?
0xArchive's per-wallet change log starts May 25, 2025 15:00 UTC. Hourly position snapshots start June 7, 2026 18:00 UTC.
Does copying a whale make money?
This guide measures positions, not returns. A copy inherits the leader's timing lag, fees and leverage choices, and the leader's results can be negative. The profile route shows realized PnL for the last week.
What happens if the wallet I copy closes a trade I never filled?
The sizing in this guide targets the leader's current positions, not their individual trades. When the leader closes, the target becomes zero. The function only returns targets: your order code has to close what you hold, then read the position back.
Are these positions real time?
No. Snapshots refresh every five minutes and the change log trails the clock. Webhooks are the near real time path.
Next steps
- Long/short account counts for any market: Hyperliquid long/short ratio and trader positioning.
- Track large TWAP orders: How to Track Hyperliquid TWAP Orders.
- Pair wallets with the forced-liquidation history guide.
- Webhook setup: Webhook Alerts, watched addresses and estimate and dry-run.
- Route reference: account positions. More on the venue at Hyperliquid data.