Hyperliquid account reads

SkillCommerce & finance

Read a Hyperliquid account from the desk computer - positions and margin, spot balances, open orders including trigger details, fills, funding paid, ledger updates, order status by oid or cloid, historical orders, portfolio history, fee tier and rate-limit budget - with curl and Python SDK examples. Read-only, needs only the account address. Use for sizing inputs, book checks, reconciliation and reviews.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Hyperliquid account reads skill

What this skill tells your AI

The instructions your AI receives, as published by galleonlabs/hypergrok-trading-desk in skills/hyperliquid-account/SKILL.md and read by ahel’s review.

All reads are POST /info, unsigned. Use the account address (the main wallet the desk trades for), never the API wallet's address: queries on an agent address return empty results.

ADDR=$HYPERLIQUID_ACCOUNT_ADDRESS
BASE=$([ "$HYPERLIQUID_NETWORK" = mainnet ] && echo https://api.hyperliquid.xyz || echo https://api.hyperliquid-testnet.xyz)
hl() { curl -sS -m 15 -X POST "$BASE/info" -H 'Content-Type: application/json' -d "$1"; }
import os
from hyperliquid.info import Info
from hyperliquid.utils import constants
from hyperliquid.utils.types import Cloid
NETWORK = os.environ.get("HYPERLIQUID_NETWORK", "testnet")
info = Info(constants.MAINNET_API_URL if NETWORK == "mainnet" else constants.TESTNET_API_URL, skip_ws=True)
ADDR = os.environ["HYPERLIQUID_ACCOUNT_ADDRESS"]

Perp account state

hl "{\"type\":\"clearinghouseState\",\"user\":\"$ADDR\"}" | jq '{
  time, accountValue: .marginSummary.accountValue, totalNtlPos: .marginSummary.totalNtlPos,
  totalMarginUsed: .marginSummary.totalMarginUsed, withdrawable, crossMaintenanceMarginUsed,
  positions: [.assetPositions[].position | {coin, szi, entryPx, positionValue, unrealizedPnl, returnOnEquity,
              liquidationPx, marginUsed, leverage, maxLeverage, cumFunding: .cumFunding.sinceOpen}]}'

Python: info.user_state(ADDR). szi is signed size; leverage is {type: cross|isolated, value, rawUsd?}; crossMarginSummary mirrors marginSummary for the cross portion. Margin ratio for a book check: crossMaintenanceMarginUsed / crossMarginSummary.accountValue (marginSummary also counts isolated margin).

Under the account's abstraction mode ({"type":"userAbstraction","user":ADDR} returns default, disabled, unifiedAccount, portfolioMargin or dexAbstraction), USDC may live in the spot state; check both when equity looks wrong.

Spot balances

hl "{\"type\":\"spotClearinghouseState\",\"user\":\"$ADDR\"}" | jq '.balances[] | {coin, token, total, hold, entryNtl}'

Python: info.spot_user_state(ADDR). hold is the amount locked in open orders.

Open orders

hl "{\"type\":\"openOrders\",\"user\":\"$ADDR\"}" | jq '.[] | {coin, side, limitPx, sz, oid, timestamp}'
hl "{\"type\":\"frontendOpenOrders\",\"user\":\"$ADDR\"}" | jq '.[] | {coin, side, limitPx, sz, origSz, oid, cloid, orderType, tif, reduceOnly, isTrigger, triggerPx, triggerCondition, isPositionTpsl, children}'

side is B (bid/buy) or A (ask/sell). Use frontendOpenOrders whenever you need to know whether an order is a stop or take-profit and whether it is position-tied. Python: info.open_orders(ADDR), info.frontend_open_orders(ADDR).

Order status by oid or cloid

hl "{\"type\":\"orderStatus\",\"user\":\"$ADDR\",\"oid\":1839201122}" | jq '{status, order: .order.status, ts: .order.statusTimestamp, o: .order.order}'
hl "{\"type\":\"orderStatus\",\"user\":\"$ADDR\",\"oid\":\"0x9f3e0c1a2b3c4d5e6f708192a3b4c5d6\"}"

Returns {"status":"order","order":{"order":{...},"status":"...","statusTimestamp":...}} or {"status":"unknownOid"}. Status vocabulary: open, filled, canceled, triggered, rejected, marginCanceled, reduceOnlyCanceled, siblingFilledCanceled, scheduledCancel, liquidatedCanceled, plus rejection reasons such as tickRejected, minTradeNtlRejected, perpMarginRejected, badTriggerPxRejected, iocCancelRejected, marketOrderNoLiquidityRejected. Python: info.query_order_by_oid(ADDR, oid), info.query_order_by_cloid(ADDR, Cloid.from_str("0x...")).

This is the reconciliation call after any send whose response was lost.

Fills

hl "{\"type\":\"userFills\",\"user\":\"$ADDR\"}" | jq '.[:20][] | {time, coin, side, px, sz, dir, closedPnl, fee, feeToken, crossed, oid, cloid, tid, hash}'
START=$(( $(date +%s000) - 86400000 ))
hl "{\"type\":\"userFillsByTime\",\"user\":\"$ADDR\",\"startTime\":$START}" | jq 'length'

crossed: true means taker; fee includes any builder fee and is negative for a rebate; dir reads like Open Long, Close Short; startPosition is the size before the fill; closedPnl is realised on that fill. userFills returns the most recent 2000; userFillsByTime returns up to 2000 per call from the last 10,000. Paginate with startTime = the last time you received (inclusive, because many fills share one millisecond) and de-duplicate by tid. Python: info.user_fills(ADDR), info.user_fills_by_time(ADDR, start_ms, end_ms).

Funding paid and ledger

hl "{\"type\":\"userFunding\",\"user\":\"$ADDR\",\"startTime\":$START}" | jq '.[] | {time, coin: .delta.coin, usdc: .delta.usdc, rate: .delta.fundingRate, szi: .delta.szi}'
hl "{\"type\":\"userNonFundingLedgerUpdates\",\"user\":\"$ADDR\",\"startTime\":$START}" | jq '.[] | {time, type: .delta.type, delta}'

Funding usdc is signed from the account's point of view. Ledger updates cover deposits, withdrawals, transfers, liquidations and vault flows. Python: info.user_funding_history(ADDR, start_ms), info.user_non_funding_ledger_updates(ADDR, start_ms).

Historical orders and TWAP fills

hl "{\"type\":\"historicalOrders\",\"user\":\"$ADDR\"}" | jq '.[:20][] | {status, statusTimestamp, o: (.order | {coin, side, limitPx, sz, origSz, oid, cloid, orderType, tif, reduceOnly, isTrigger, triggerPx})}'
hl "{\"type\":\"userTwapSliceFills\",\"user\":\"$ADDR\"}" | jq '.[:5]'

Up to 2000 recent orders with their final status; the Trade Reviewer's source for "was the stop on the exchange the whole time". Python: info.historical_orders(ADDR), info.user_twap_slice_fills(ADDR).

Portfolio history, fees, rate limit, role

hl "{\"type\":\"portfolio\",\"user\":\"$ADDR\"}" | jq '.[] | select(.[0]=="day" or .[0]=="week") | {period: .[0], pnl: .[1].pnlHistory[-1], value: .[1].accountValueHistory[-1], vlm: .[1].vlm}'
hl "{\"type\":\"userFees\",\"user\":\"$ADDR\"}" | jq '{userCrossRate, userAddRate, userSpotCrossRate, userSpotAddRate, activeReferralDiscount, activeStakingDiscount}'
hl "{\"type\":\"userRateLimit\",\"user\":\"$ADDR\"}" | jq .
hl "{\"type\":\"userRole\",\"user\":\"$ADDR\"}" | jq .
hl "{\"type\":\"extraAgents\",\"user\":\"$ADDR\"}" | jq '.[] | {address, name, validUntil}'

portfolio gives PnL and account-value history per period (day, week, month, allTime, and perp-only variants); userFees gives the effective taker (userCrossRate) and maker (userAddRate) rates for the strategy lab and reviews; userRateLimit shows the address's action budget (nRequestsUsed, nRequestsCap, cumVlm); userRole classifies an address (user, agent, vault, subAccount, missing); extraAgents lists approved API wallets with expiry. Python: info.portfolio(ADDR), info.user_fees(ADDR), info.user_rate_limit(ADDR), info.user_role(ADDR), info.extra_agents(ADDR).

Per-market account data (leverage setting, available to trade, max trade sizes) without opening a position: hl "{\"type\":\"activeAssetData\",\"user\":\"$ADDR\",\"coin\":\"ETH\"}".

Sub-accounts and vaults (read only on this desk)

{"type":"subAccounts","user":ADDR} lists sub-accounts with their states; {"type":"userVaultEquities","user":ADDR} lists vault deposits; {"type":"vaultDetails","vaultAddress":"0x..."} describes a vault. The desk reads these for completeness and does not move funds between them.

Book check recipe (Risk Manager)

  1. clearinghouseState for equity, positions, margin, liquidation prices.
  2. frontendOpenOrders for protection (reduce-only triggers per position) and orphans.
  3. metaAndAssetCtxs for mark prices to compute liquidation distance.
  4. userFunding since start of day for funding paid.
  5. portfolio or the journal for start-of-day equity, to compute day PnL against the daily loss stop.

Report per desk-risk-limits section 3.

Pitfalls

  • Querying the API wallet's address. Everything comes back empty and looks like "no positions".
  • Assuming openOrders shows trigger details; it does not. Use frontendOpenOrders.
  • Reading userFills and forgetting the 2000-item window; use userFillsByTime with pagination for reviews.
  • Treating withdrawable as free margin for new positions; use accountValue - totalMarginUsed with headroom, and check the tier.
  • Mixing networks: a mainnet address on the testnet endpoint is a different (probably empty) account.

Signals

GitHub stars
61
Forks
12
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
hyperliquid-account
Source
github.com/galleonlabs/hypergrok-trading-desk