Hyperliquid TWAP & Scaled Orders: Building Execution Algos
A senior engineer's guide to Hyperliquid TWAP orders and custom scaled/iceberg execution — cut slippage on large perp entries with native and DIY algos.
Try to buy 40 BTC of perp exposure on Hyperliquid in a single market IOC order and you'll watch the fill walk three, four, sometimes eight ticks past mid before it clears. The book on HL is deep for the majors but thin the moment you step outside the top few levels, and every level you eat prints to the tape for anyone watching the L2 feed. Splitting that entry over time isn't optional above a certain size — it's the difference between a clean 2 bps of impact and a 15 bps donation to whoever's sitting on resting liquidity.
This is a guide to two ways of doing that on Hyperliquid: the native TWAP order type the exchange ships, and rolling your own scaled/iceberg logic when the native one doesn't give you enough control.
Native TWAP: what it actually does
Hyperliquid has a first-class TWAP order type. You submit it once, and the matching engine slices your total size into sub-orders released on a fixed cadence over the duration you specify. It's not a client-side loop — the exchange holds the parent and manages the children, which means it survives your websocket dropping and doesn't count as a fresh order every slice for rate-limit purposes.
The mechanics worth knowing before you use it:
- Sub-orders fire roughly every 30 seconds. Your
minutesparameter sets the total window, so a 60-minute TWAP on 40 BTC releases ~2 BTC chunks, 120 of them. - Each slice is an aggressive order that crosses the spread, but Hyperliquid caps how far it will chase. There's a 3% price band per slice relative to the last sub-order — if the market runs past that, the slice is skipped rather than filled at any price. You can end up under-filled if you TWAP into a fast move, which is a feature, not a bug, but you have to account for it.
- The
randomizeflag jitters the slice timing so you're not printing a metronome that a predatory bot can front-run.
Here's a minimal submission through the Python SDK:
from hyperliquid.exchange import Exchange
from hyperliquid.utils import constants
ex = Exchange(wallet, constants.MAINNET_API_URL)
# 40 BTC long, spread over 60 minutes, jittered
resp = ex.twap_order(
name="BTC",
is_buy=True,
sz=40.0,
reduce_only=False,
minutes=60,
randomize=True,
)
twap_id = resp["response"]["data"]["status"]["running"]["twapId"]
Cancelling is a single call against that twapId, and any unfilled remainder just stops — nothing rests on the book afterward. If you're wiring this into a larger system, the same auth and signing patterns from our Hyperliquid API bot guide covering Python and Rust apply directly here; the TWAP endpoint is just another signed action.
When native TWAP is the wrong tool
The native order is time-sliced and time-sliced only. It doesn't care about the book. If liquidity dries up on slice 47, it still crosses the spread and pays whatever the 3% band allows. It also can't be made passive — every child is a taker, so you're paying taker fees on the full notional and never earning the maker rebate. For a 40 BTC entry at HL's fee schedule, that gap is real money over a month of entries.
That's the moment you build your own.
Rolling your own scaled and iceberg execution
A scaled order is dead simple in concept: instead of slicing by time, you place a ladder of resting limit orders across a price range and let the market come to you. An iceberg is the same idea but you only expose one clip at a time, replenishing as fills come in so the book never shows your full hand.
The control you're buying is the ability to be a maker. You post inside the spread or at mid, earn the rebate instead of paying the take, and only cross when the clock runs out on your urgency budget. The tradeoff is fill risk — passive orders don't fill if the market walks away — so you need a fallback.
A pattern that works well in production:
async def iceberg_buy(ex, coin, total_sz, clip, band_bps, timeout_s):
filled = 0.0
deadline = time.time() + timeout_s
while filled < total_sz and time.time() < deadline:
book = get_l2(coin)
best_bid = book["bids"][0]["px"]
# sit one tick inside the bid to stay maker
px = round_to_tick(best_bid * (1 + 0.0001))
sz = min(clip, total_sz - filled)
oid = ex.order(coin, True, sz, px, {"limit": {"tif": "Alo"}})
done = await wait_fill_or_move(oid, best_bid, band_bps)
filled += done
# sweep any remainder aggressively at the buzzer
if filled < total_sz:
ex.market_open(coin, True, total_sz - filled)
Two things make or break this loop. First, the Alo (add-liquidity-only) time-in-force — it rejects the order outright if it would cross and take, guaranteeing you stay maker. If you submit a plain limit and the market moves into you, you become a taker by accident and blow your fee assumptions. Second, the remainder sweep: your passive clips will not always finish inside the window, and you need an explicit, aggressive close-out so a half-filled entry doesn't leave you carrying unwanted delta.
The gotchas that cost you
- Tick and lot rounding. Hyperliquid enforces per-asset
szDecimalsand price tick sizes. Compute your clip and price against the asset meta, not a hardcoded constant, or the exchange rejects the order and your loop stalls silently. Pullmetaonce at startup and cache it. - Rate limits. Every replenish is an order action. An aggressive iceberg that reposts every second on ten assets will hit the address rate limit fast. Batch where you can, and prefer modifying an existing order (
modify) over cancel-plus-new when you're only nudging price. - Nonce ordering. If you're firing orders concurrently, out-of-order nonces get rejected. Serialize signing through a single async lock even if the network calls fan out.
- Self-trade. If you also run a maker on the same book — say the kind of quoting engine behind our Hyperliquid market-making service — your iceberg can lift your own resting quote. Tag both with a shared client identifier and skip levels where you're the resting side.
Choosing between them
Reach for native TWAP when you want fire-and-forget, you're fine paying taker fees, and your priority is guaranteed participation over time rather than best possible price. It's the right default for scheduled rebalances and for entries you don't want to babysit.
Build the scaled/iceberg version when fees matter at your size, when you have a genuine urgency budget you can express as a timeout, and when you want the book impact of a single clip instead of a visible time series of taker prints. Most desks we build for end up running both: native TWAP for the boring flow, custom execution for the entries that actually move the needle.
Execution quality compounds. A perps strategy that's marginal on paper — a funding-rate carry between HL and a CEX, or a directional perps bot — often lives or dies on whether you're leaking 10 bps on every entry and exit. The same discipline that makes the HLP vault strategy work at scale applies to your own fills: measure implementation shortfall against arrival mid on every order, log it, and tune your clip size and band until the number stops shrinking. If you'd rather have this measured and tuned for you against your actual flow, that's the kind of thing our Hyperliquid perps execution work is built to handle.
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