Hyperliquid Risk Limits: Max Position Sizes and How to Calculate Them
Hyperliquid's per-asset and portfolio-level risk limits, how max leverage tiers scale with notional size, and how to build a pre-trade risk calculator that prevents order rejection at the API level.
Hyperliquid's risk model is tighter and more dynamic than most perp DEXs, and if you don't account for it at the order-construction layer you will hit silent rejections mid-strategy. The exchange publishes the rules, but the documentation leaves out a lot of the production detail that matters when you're running automated bots at scale. This article walks through exactly how the limits work, how to derive the per-asset max position for any account state, and how to wire a pre-trade check that blocks bad orders before they ever hit the wire.
How the Leverage Tier System Works
Hyperliquid uses a notional-stepped leverage cap rather than a single max leverage per asset. Each asset has a table that looks like this (BTC-PERP as of mid-2025):
| Max Notional (USD) | Max Leverage |
|---|---|
| 0 – 50,000 | 50x |
| 50,001 – 250,000 | 25x |
| 250,001 – 1,000,000 | 10x |
| > 1,000,000 | 3x |
The thresholds and leverage caps are per-asset and change over time, so you cannot hardcode them. Fetch them from the /info endpoint under meta.universe[i].maxLeverage paired with the separate leverageTiers field returned for each asset. Assets with thinner books have far more aggressive step-downs — some illiquid alts drop to 3x at just $25k notional.
The practical consequence: a position that was valid at 20x becomes invalid as the mark price moves and notional grows. The exchange will not auto-reduce your leverage, but it will reject any add-to-position orders once your current notional exceeds the tier boundary for your chosen leverage.
Portfolio-Level Margin: Cross vs. Isolated
Every account can mix cross-margin and isolated-margin positions on the same Hyperliquid account. This is where most bugs appear.
In cross-margin, all cross positions share a single margin pool. The free margin available to a new cross position is:
free_margin = account_value - sum(initial_margin, cross positions) - sum(open_order_reserved, cross positions)
account_value here is mark-to-market, not deposit balance. Unrealized PnL contributes, but Hyperliquid applies a haircut on unrealized gains before counting them toward new initial margin — the haircut percentage is published in meta.haircutFactor and typically sits around 0.8.
In isolated-margin, the position has its own margin bucket. Rejected orders in isolated mode are usually because:
- You tried to set leverage above the tier cap for your current notional.
- The isolated margin you're allocating is below the required initial margin at the requested size.
- The position would breach the max open interest cap for that asset, which is separate from your personal leverage limit.
Calculating Max Position Size Pre-Trade
Given current account state, the max position you can open at leverage L on asset A is:
max_notional = min(
tier_max_notional(A, L), // leverage tier boundary
free_margin * L * haircutFactor, // available margin budget
(OI_cap - current_OI) * mark_price // global OI headroom
)
max_contracts = floor(max_notional / (mark_price * contract_size))
tier_max_notional(A, L) is the upper bound of the tier whose max leverage is >= L. If you ask for 15x on BTC, the applicable tier is "0–50k", so the tier cap is $50,000 notional. If your available margin is $5,000, the margin budget at 15x is $75,000 — meaning the tier cap binds first.
For assets with a contract_size other than 1 (most index alts), failing to divide correctly gives you an off-by-one that either wastes capacity or over-sizes the order. Pull meta.universe[i].szDecimals and use 10 ** -szDecimals as your minimum lot.
Building the Pre-Trade Risk Calculator
Here is a minimal TypeScript sketch of the check we run before constructing any order:
interface AssetMeta {
maxLeverage: number;
leverageTiers: { maxNotional: number; maxLeverage: number }[];
szDecimals: number;
oiCap: number; // in contracts
}
function maxContracts(
meta: AssetMeta,
markPrice: number,
leverage: number,
freeMarginUsd: number,
currentOiContracts: number,
haircutFactor = 0.8,
): number {
// Find the tier that allows this leverage
const tier = meta.leverageTiers
.filter(t => t.maxLeverage >= leverage)
.sort((a, b) => a.maxNotional - b.maxNotional)[0];
if (!tier) throw new Error(`Leverage ${leverage}x not permitted on this asset`);
const tierNotional = tier.maxNotional;
const marginBudget = freeMarginUsd * leverage * haircutFactor;
const oiHeadroom = (meta.oiCap - currentOiContracts) * markPrice;
const maxNotional = Math.min(tierNotional, marginBudget, oiHeadroom);
const lotSize = Math.pow(10, -meta.szDecimals);
const rawContracts = maxNotional / markPrice;
return Math.floor(rawContracts / lotSize) * lotSize;
}
Call this immediately after fetching a fresh clearinghouse_state snapshot and before size validation on your order. Do not cache the output — mark price, OI, and unrealized PnL all shift between heartbeats, and a stale check is worse than no check because it gives false confidence.
Common Failure Modes in Production
A few patterns we see repeatedly when auditing systems that were built without a tight pre-trade layer:
- Skipping the OI cap check. Personal leverage limits pass but the asset-level OI is near its ceiling. The order rejects with a generic margin error rather than an explicit OI message, so the bot logs it as a margin failure and does not retry with a smaller size.
- Using wallet balance instead of account value. Unrealized gains on existing positions inflate the real buying power. Using raw deposit balance underestimates capacity; using raw mark-to-market without the haircut overestimates it.
- Not re-fetching tier tables after a config change. Hyperliquid adjusts tier boundaries on illiquid assets without advance notice. A production bot should pull
meta.universefresh on each run cycle, or at minimum on startup and after any 4xx response. - Ignoring isolated margin reservations. Even a zero-size isolated position with an open order reserves margin. The
clearinghouse_stateresponse includesmarginSummary.totalNtlPosand open order reservations separately — sum them correctly.
The math here is not complicated, but the details are fiddly enough that skipping it causes real problems under live conditions. Getting this layer right means your strategy logic never has to handle order-rejection error paths for size violations.
If you want a pre-trade risk layer like this built and integrated into your Hyperliquid stack, get in touch — we scope, build, and operate it end-to-end.
Need a bot like this built?
We design, build and run trading bots on Solana, Hyperliquid and Polymarket.
Start a projectMore from the blog
Public vs Paid RPC: How to Run a Fair Benchmark
A fair comparison of public and paid RPC endpoints must account for quotas, workload, freshness and support guarantees.
Read articleRPC Benchmark Percentiles Explained: p50, p95 and p99
Why averages hide the latency spikes that break trading bots, wallets and production blockchain applications.
Read articleHyperliquid REST vs WebSocket Benchmark: What to Measure
A benchmark plan for Hyperliquid market data that separates request latency from streaming freshness and recovery.
Read article