360 Secure SkillHub
360 安全技能中心

HyperGrok Hyperliquid Account

通过 Hyperliquid 的只读 /info 接口和 Python SDK,查询账户的永续与现货余额、持仓与保证金、挂单与触发单、成交、资金费、账本、费率、限额及子账户和 vault 信息,用于对账与风险检查;不包含下单或资金转移。

galleonlabs
热度 52.89
最近更新 2026-10-13 18:03:45
分类:数据依赖:第三方 API / 代码执行应用场景:财务 / 数据处理 / 报告撰写 / 研究

安装技能

复制下面的话术发给你的 Agent,它会通过 secure-skills 检索并安装该技能。

执行命令安装此 skill: npx secure-skills-cli add hypergrok-hyperliquid-account
Metadata
name
hyperliquid-account
description
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.
license
MIT
metadata
{"version":"1.0.1","author":"Galleon Labs","category":"hyperliquid","network-default":"testnet"}

Hyperliquid account reads

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).

Read the account's abstraction mode first: {"type":"userAbstraction","user":ADDR} returns default, disabled, unifiedAccount, portfolioMargin or dexAbstraction. Record it with the sizing inputs. The separate spot/perp balance model must not be assumed for unified, portfolio-margin or DEX-abstraction accounts. Check the mode's documented collateral basis and relevant account states, and compare with activeAssetData for trading capacity. Do not add spot USDC to perp equity or use spot total - hold as universal free margin: collateral can be shared, and a balance alone does not establish capacity. If the mode's equity or margin interpretation cannot be verified, report it as unavailable and reject new risk.

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\"}".

To verify signing scope, query userRole for the public API wallet address separately: require role: agent with data.user equal to the ticket's account, on the ticket's network, and check its unexpired approval in extraAgents. Record those fresh reads before sending. A successful unsigned /info account query works without a key and cannot attest to an API wallet's authority.

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.