Prerequisites
- Python 3.8 or higher
hyperliquid-python-sdkinstalled (pip install hyperliquid-python-sdk)- Reliable Hyperliquid RPC endpoint (sign up for free)
TWAP order structure
TWAP orders aren’t yet available in the Python SDK’s high-level methods. According to the Hyperliquid API documentation, you need to use raw API calls with thetwapOrder action type:
TWAP parameters explained
a(asset ID) — for spot:10000 + indexfrom spot metadata. For perps: use the perp index directly. Get index frominfo.spot_meta_and_asset_ctxs()orinfo.meta().b(buy/sell) —Truefor buy,Falsefor sell. Boolean, not string.s(size) — total size to execute. Usefloat_to_wire()to avoid trailing zeros that cause API rejections.m(duration in minutes) — how long to spread execution. Range: 1–1440 (24 hours). Longer duration = lower market impact but more price exposure.t(randomize) —False= evenly spaced intervals (30-min TWAP → child orders every ~30 seconds).True= randomized intervals, harder for algorithms to detect.r(reduce-only) —Trueprevents opening new positions, only closes existing ones.
Understanding TWAP execution
When you submit a TWAP order, Hyperliquid’s exchange backend calculates child order sizing and timing, then executes incrementally. You receive WebSocket updates for each fill. Example with real numbers — 30-minute TWAP for 100 units,randomize=False:
- Duration — 1800 seconds
- Child order interval — ~30 seconds (1800 / 60 ≈ 30)
- Child order size — ~1.67 units (100 / 60 ≈ 1.67)
- Total child orders — ~60
randomize=True matters: In competitive markets, other algorithms detect regular 30-second intervals and front-run your orders. Randomization spreads child orders across the same total duration but at unpredictable intervals. Average still ~30 seconds, but actual intervals vary from 10–50 seconds.
Placing TWAP orders with validation
Hyperliquid enforces precision and minimum size rules. Each asset hasszDecimals (size precision) and a $10 minimum notional value requirement:
TWAP order placed successfully and TWAP ID received for tracking
Why
float_to_wire()? Prevents trailing zeros that cause API rejections. 100.0 becomes "100" string format.Monitoring TWAP execution via WebSocket
According to the WebSocket API documentation, theuserEvents channel delivers TWAP updates in the twapHistory array:
state (order details) and status (execution phase):
TWAP status lifecycle
activated— TWAP accepted by exchange, child order execution begins.completed— all size executed successfully.terminated— exchange stopped execution early:- Insufficient balance — account ran out of funds mid-execution
- Market conditions — extreme volatility or order book issues
- Execution failure — child orders consistently rejected
canceled— you manually stopped the TWAP using thetwapCancelAPI action.
userEvents.fills array for each child order. Correlate these with the active TWAP to track real-time progress.
Canceling TWAP orders
Cancel active TWAPs using raw API withtwapCancel action:
You can only cancel TWAPs still executing (status
activated in WebSocket events). Completed or terminated TWAPs return an error.Tracking child order fills
Individual child order fills appear inuserEvents.fills alongside regular orders. Correlate with active TWAPs:
Reduce-only TWAPs for position exits
Set"r": True in the TWAP action to prevent accidentally increasing positions when closing:
"r": True, you’d flip from 10 BTC long to 5 BTC short.
Mirroring TWAP orders in copy trading
Copy trading with TWAP orders presents unique challenges compared to regular limit orders. The fundamental issue: TWAP IDs are asymmetric. When you place a TWAP, the API response includes the TWAP ID:twapHistory events don’t include the TWAP ID:
twapHistory events contain a state object with TWAP properties but no twapId field for tracking.
This asymmetry creates problems for copy trading:
- You can’t use TWAP IDs to deduplicate events (no ID in WebSocket)
- You can’t map leader TWAP IDs to follower TWAP IDs (you never see leader’s ID)
- You can cancel your own TWAPs (you have the follower TWAP ID from placement), but you need a different strategy to know when to cancel based on leader activity
The combination-based tracking strategy
According to the WebSocket API documentation,twapHistory events in the userEvents channel contain a state object with TWAP properties but no unique identifier for tracking.
The solution: Create combination keys from TWAP properties to detect duplicates:
A leader might place two identical 30-minute BTC TWAPs with different sizes (for example, 0.5 BTC and 1.0 BTC). Without size in the combination, they’d have the same key and you’d only mirror one.
Handling same-wallet testing scenarios
When testing with the same wallet as both leader and follower, you need to prevent mirroring your own follower TWAPs. But since WebSocket events don’t include TWAP IDs, you can’t simply check “is this TWAP ID in my follower list?” The solution: Track follower TWAP combinations separately with adjusted sizes:1
Check if processed as leader
Is this combination in
leader_twap_combinations? If yes, already processed as leader TWAP.2
Check if own follower order
Is this combination in
follower_twap_combinations? If yes, this is our own follower TWAP—skip it.In production with separate wallets, leader and follower sizes don’t overlap in the same WebSocket feed—you don’t need this follower combination check. This is purely for development convenience.
Placing follower TWAPs with adjusted sizing
When mirroring a leader’s TWAP, you need to maintain the same time parameters (duration, randomization) while adjusting size based on your capital allocation:Canceling follower TWAPs when leader cancels
When a leader cancels or terminates a TWAP, you need to cancel the corresponding follower TWAP. What you have:- Follower TWAP ID (from placement response — stored when you placed it)
- Leader TWAP combination (from WebSocket event properties)
- A way to map: “when this leader combination cancels → cancel this follower TWAP ID”
If the leader places the same TWAP combination later (same asset, same parameters, same size), you want to mirror it again. Removing it from
leader_twap_combinations allows reprocessing.When to mirror vs when to skip
Not all leader TWAPs should be mirrored. Consider these scenarios: Mirror when:- TWAP status is
activated(new TWAP just started) - Asset is spot (unless you specifically want to mirror perp TWAPs)
- Combination hasn’t been processed yet
- You have sufficient balance for the follower TWAP
- TWAP status is
completed(already finished, no action needed) - Combination is in
leader_twap_combinations(already mirrored) - Combination is in
follower_twap_combinations(this is your own follower TWAP) - Asset is perpetual and you only mirror spot trades
- TWAP size is too small to meet minimum notional after adjusting for your allocation
- Leader TWAP status changes to
canceledorterminated - You have a corresponding follower TWAP tracked in
twap_mappings
Summary
TWAP orders split large trades into smaller chunks over time to reduce market impact. Use raw API calls withtwapOrder action (asset ID: 10000 + index for spot), monitor execution via userEvents WebSocket subscription, and ensure each child order meets the $10 minimum notional. For copy trading, track TWAPs using combination keys (coin_side_minutes_randomize_size) since TWAP IDs aren’t available in WebSocket events.
The complete implementation is available in the Chainstack Hyperliquid trading bot repository.
Related resources
- Copy trading with spot orders — Build a copy trading bot that mirrors spot trades
- Authentication guide — Authenticate with Hyperliquid exchange API
- L1 action signing — Understand L1 action signing for trading operations
- API reference — Explore the complete Hyperliquid API reference