All articles
Risk·October 16, 2025·5 min read

Setting Position Limits and Exposure Caps on Hyperliquid

Uncapped exposure is how HFT bots blow up accounts overnight. Learn how to enforce per-asset position limits and total portfolio exposure caps using Hyperliquid's API and on-chain margin accounting.

Uncapped exposure is the most common way automated trading systems blow up accounts — not bad alpha, not execution slippage, but a risk layer that was never built. Hyperliquid's on-chain perpetuals architecture gives you the raw materials to enforce hard limits at both the per-asset and portfolio level, but the exchange won't do it for you. Here is exactly how to wire it in.

Understanding What Hyperliquid Exposes

Hyperliquid runs a fully on-chain order book on its own L1. Every position and margin balance is readable from the info endpoint in real time — no need to reconcile with a CEX's private ledger or trust delayed WebSocket snapshots.

The two endpoints you care about most:

  • /info with type: "clearinghouseState" — returns your cross-margin account summary, including marginSummary.accountValue, marginSummary.totalNtlPos, and crossMaintenanceMarginUsed.
  • /info with type: "openOrders" — returns working orders with size and side, letting you calculate your pending notional exposure before fills land.

totalNtlPos is the sum of absolute notional values across all open positions. That single field is your gross exposure number. If you are running a delta-neutral strategy you will want to track totalNtlPos and net delta separately — they tell different stories.

Defining Your Limit Hierarchy

Before writing a single line of enforcement code, write down three numbers per asset and two numbers at the portfolio level:

Per-asset:

  1. max_position_notional — the hard cap on your absolute exposure in one market (e.g., $50,000 notional on BTC-PERP)
  2. max_order_size — the largest single order you will send (prevents fat-finger fills from clearing your headroom instantly)
  3. max_open_orders — a secondary check that bounds how many resting orders can stack up on one side

Portfolio:

  1. max_gross_exposure — the ceiling on totalNtlPos across all markets (e.g., $200,000)
  2. max_margin_utilization — e.g., 60% of accountValue used as crossMaintenanceMarginUsed before you stop opening new positions

These are not suggestions; they go into a config file that your bot reads at startup and reloads on SIGHUP without a restart.

Enforcing Limits Before Order Submission

The right place to enforce limits is in a pre-flight check that gates every order before it touches the API. Structure it as a synchronous function that returns either approved or a reason string — never a boolean, because you will want the reason in your logs.

def preflight(order, state, config):
    asset = order["coin"]
    side_sign = 1 if order["is_buy"] else -1
    new_notional = abs(order["sz"] * order["limit_px"])

    # Per-asset position check
    current = state["positions"].get(asset, 0.0)  # signed notional
    projected = current + side_sign * new_notional
    if abs(projected) > config["limits"][asset]["max_position_notional"]:
        return f"REJECT: {asset} projected notional {projected:.0f} exceeds cap"

    # Portfolio gross exposure check
    pending_gross = state["total_ntl_pos"] + new_notional
    if pending_gross > config["max_gross_exposure"]:
        return f"REJECT: gross exposure {pending_gross:.0f} exceeds portfolio cap"

    # Margin utilization check
    if state["margin_used"] / state["account_value"] > config["max_margin_utilization"]:
        return "REJECT: margin utilization ceiling hit"

    return "approved"

The state object here is populated from a polling loop hitting clearinghouseState every 500ms — fast enough for most strategies, with a mutex protecting reads during order submission. For strategies running sub-100ms tick frequencies you will want the WebSocket fills feed instead, maintaining a local position shadow and reconciling against the REST snapshot every few seconds.

Handling the Async Gap

Hyperliquid's order acknowledgement is fast, but there is a window between when you send an order and when it appears in clearinghouseState. During that window a second order can pass preflight and you can end up with 2x your intended position.

The fix is a pending-orders register: a thread-safe dict keyed by client order ID (cloid) that tracks submitted-but-not-yet-confirmed notional. Your preflight check includes pending notional in its calculations. When a fill or cancel confirmation arrives on the WebSocket, you remove the entry. If an order is not confirmed within a configurable timeout (typically 2-3 seconds on Hyperliquid), you treat it as failed and drop it from the register — then reconcile against REST before placing anything new.

This is the part that most open-source bot frameworks skip. It is also the part that causes the 3am incident.

Hard Stops vs. Soft Limits

Soft limits halt new order submission. Hard stops go further: they cancel all open orders and optionally flatten positions. You need both.

Trigger hard stops on:

  • account_value dropping below a floor (e.g., 80% of session-start value)
  • Any single position moving more than N% against you in a defined window
  • The clearinghouseState polling loop failing to return a valid response for more than 5 consecutive seconds

Use Hyperliquid's cancel all action (cancelAllOrders) rather than iterating individual cancels — it is a single signed transaction and clears the book atomically from your account's perspective. For flattening, market orders at slippage: 0.05 (5% worst-case) are acceptable in a stop scenario; you are prioritizing speed over price.

Tie the hard-stop logic to a separate process, not the main trading loop. If the trading process hangs, the stop process still fires. This is standard practice for any production trading bot and not optional.

Margin Mode Considerations

Hyperliquid supports both cross-margin and isolated-margin per position. Cross-margin makes portfolio-level exposure tracking straightforward — one accountValue, one totalNtlPos. Isolated margin lets you cap loss on a single position structurally, but it complicates portfolio-level accounting because you have to sum isolated margin allocations manually.

In practice: use cross-margin for multi-asset strategies where you actively manage gross exposure, use isolated margin only when you want a position that is explicitly ring-fenced with a fixed loss budget and no dynamic rebalancing. Mixing modes in the same account without careful accounting is where silent risk accumulates.


If you want a production risk layer built to these specifications — integrated with your existing Hyperliquid strategy — talk to us.

Need a bot like this built?

We design, build and run trading bots on Solana, Hyperliquid and Polymarket.

Start a project
#risk#hyperliquid#trading-bots#position-management#exposure-caps#hft#perpetuals