Skip to main content
TWAP orders split large trades into smaller chunks over time to reduce market impact. Instead of consuming the entire order book with one trade, you execute incrementally—minimizing slippage and front-running risk. This guide shows you how to place TWAP orders on Hyperliquid and monitor execution via WebSocket.
Prerequisites

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 the twapOrder action type:

TWAP parameters explained

  • a (asset ID) — for spot: 10000 + index from spot metadata. For perps: use the perp index directly. Get index from info.spot_meta_and_asset_ctxs() or info.meta().
  • b (buy/sell) — True for buy, False for sell. Boolean, not string.
  • s (size) — total size to execute. Use float_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) — True prevents 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
If liquidity is thin and some child orders only partially fill, the TWAP adjusts remaining size across future child orders. Why 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 has szDecimals (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.
Each child order must meet the $10 minimum. A 30-minute TWAP creates ~60 child orders. If your TWAP is $500 total, each child = $500 / 60 ≈ $8.33 (rejected). Minimum safe TWAP: $600+ for 60 child orders.

Monitoring TWAP execution via WebSocket

According to the WebSocket API documentation, the userEvents channel delivers TWAP updates in the twapHistory array:
Each TWAP event contains 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 the twapCancel API action.
During execution, you’ll also receive individual fill events in the 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 with twapCancel 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 in userEvents.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:
You hold a 10 BTC long position. You place a reduce-only TWAP sell for 15 BTC. The TWAP stops after selling 10 BTC—it won’t open a 5 BTC short position. Without "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:
But when monitoring via WebSocket, the twapHistory events don’t include the TWAP ID:
According to the WebSocket API documentation, 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 solution: Use TWAP properties as composite keys instead of relying on IDs.

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

This section describes a development shortcut for testing. In production with separate leader and follower wallets, this infinite loop problem won’t exist.
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:
Leader combination uses leader’s size, follower combination uses follower’s (potentially different) size. When a WebSocket event arrives, you check both:
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:
The leader chose those parameters based on market conditions and their execution strategy. If the leader uses 30 minutes with randomization, they’re likely trading in a market where that timing avoids front-running. Your follower TWAP benefits from the same strategy.

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)
What you need:
  • A way to map: “when this leader combination cancels → cancel this follower TWAP ID”
Use combination-based mappings to bridge the gap:
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
Skip when:
  • 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
Cancel follower TWAP when:
  • Leader TWAP status changes to canceled or terminated
  • You have a corresponding follower TWAP tracked in twap_mappings
This logic ensures you mirror leader strategy changes (canceling TWAPs early) while avoiding duplicate mirrors and infinite loops.

Summary

TWAP orders split large trades into smaller chunks over time to reduce market impact. Use raw API calls with twapOrder 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.
Last modified on April 13, 2026