A TWAP (time-weighted average price) trigger splits a total quantity into slices and places one
slice per interval over a duration. Use it to work a larger order without sending it to the book at
once. A TWAP does not watch a price and does not guarantee the market's average price or a full
fill. Create one with CreateTrigger (POST /v1/triggers)
and a twap strategy.
Fields
| Field | Rule |
|---|---|
qty_scaled | Required, positive. The total quantity of all slices |
twap.side | Required: BUY or SELL |
twap.duration_ms | Required, at least 1000 (1 second) |
twap.slice_interval_ms | Required, at least 100 and at most duration_ms |
twap.market_ioc | Market slices. Optional max_slippage_ticks or max_slippage_bps (1 to 10000 for a buy, 1 to 9999 for a sell) per slice |
twap.limit_gtc | Limit slices at price_ticks, on the Pair's tick size. Takes no slippage cap |
Set exactly one of market_ioc and limit_gtc. fee_asset, self_trade_prevention_mode, and client_trigger_id follow the same rules as other triggers. In REST, the limit price is limitGtc.price and the quantity qty, both decimal strings; the durations stay integers.
How slices are planned and sized
The number of slices is duration_ms divided by slice_interval_ms, rounded up, and at most
1,000,000. The first slice
is placed when the TWAP is accepted, and each later slice one interval after the previous one.
Each slice's quantity is decided when it is due: the quantity not yet filled or open, divided by the slices left, rounded down to the Pair's quantity step. Quantity that an earlier slice did not fill moves into later slices, and the last slice takes everything that remains.
Because slices never shrink while they fill, the first slice is the smallest. A create whose first
slice could never pass the Pair's order limits is rejected with that check's code, and nothing is
created: STEP_SIZE when it rounds down to zero, or MIN_QUANTITY or MIN_NOTIONAL. A market
slice is valued at its slippage bound from the current price.
A TWAP buys 1 BTC with duration_ms 600000 and slice_interval_ms 60000, so it plans 10 slices.
The Pair's quantity step is 0.0001 BTC in this example.
- The first slice buys 0.1 BTC and fills 0.1.
- The second slice buys 0.1 BTC and fills only 0.04. The remaining 0.86 BTC is spread over the 8 slices left.
- The third slice buys 0.1075 BTC, and so on, until the tenth slice places what is left.
Market and limit slices
| Slices | Behaviour |
|---|---|
market_ioc | Each slice takes liquidity at once, within its slippage cap of the reference price when it is placed. Without a cap, the Pair's default market slippage applies. Any unfilled part is canceled at once |
limit_gtc | Each slice rests at your limit price. When the next slice is due, the previous one is canceled first, so only one slice rests at a time |
The slippage cap protects each slice on its own. It does not bound how far the price moves over the whole duration. See Market order price protection.
Funds
A TWAP reserves nothing when it is created. Each slice is funded when it is placed, like an order you send. A slice that your available balance cannot cover is rejected, and the TWAP fails.
How a TWAP ends
A live TWAP is RUNNING. twap_state shows slice_idx, slice_count, and executed_qty_scaled, the quantity its slices have filled.
| Final status | When |
|---|---|
COMPLETED | Every slice was placed and none is still open, or the slices filled the total quantity |
FAILED | A slice was rejected. failure_reason is the slice's reason, such as INSUFFICIENT_FUNDS |
CANCELED | You canceled the TWAP, or its Pair was disabled (PAIR_DRAIN_TWAP) or halted (MARKET_HALTED). Its open slice is canceled with it |
executed_qty_scaled with the total quantity.Slices placed before a TWAP fails or is canceled remain real orders, and their fills stand. A failed TWAP does not retry the rejected slice and places no further slices.
The duration is a plan, not a hard deadline. A slice that waits, for example for placement budget or for the previous limit slice to be canceled, delays the slices after it.
Managing a TWAP
A TWAP takes only CancelTrigger. Pause, resume, and modify are refused with TRIGGER_NOT_MODIFIABLE; cancel and create a new TWAP to change it.
Each slice is an ordinary order with the client order ID trg:<trigger_id>:<child_seq>, and EVENT_FIRED names it. You can amend or cancel a slice like any order, but you cannot raise it above
what the TWAP still has to execute: that is refused with CHILD_QTY_EXCEEDS_REMAINING. Canceling a
slice does not cancel the TWAP. A cancel-all cancels the open slice and leaves the TWAP running.
Limits
An account can run up to 20 TWAPs on one Pair at once, and they count toward its 100-trigger limit
on that Pair. A TWAP beyond either limit is refused with MARKET_TWAP_LIMIT_EXCEEDED or MARKET_TRIGGER_LIMIT_EXCEEDED, and nothing is created. When the Pair has no room for another TWAP
across all accounts, the create is refused with TWAP_CAPACITY_REACHED; retry later.
Creating a TWAP costs one unit of TRADING_PLACE, and each slice costs one more when it is placed.
A slice that meets an empty budget, or an account under an automatic repair, waits and is placed
shortly; the TWAP records EVENT_FIRE_REFUSED for the refused attempt and stays RUNNING. See Rate limits.
Example
A TWAP that buys 1 BTC over 10 minutes in market slices, each capped at 0.5% slippage.
POST /v1/triggers
{
"trigger": {
"symbol": "BTC-USDT",
"qty": "1",
"clientTriggerId": "twap-btc-1",
"twap": {
"side": "BUY",
"durationMs": 600000,
"sliceIntervalMs": 60000,
"marketIoc": { "maxSlippageBps": 50 }
}
}
}/triggers.v1.TriggersService/CreateTrigger, assuming Pair ID 1 and a base_quantity_scale of 8.
{
"trigger": {
"symbolId": 1,
"qtyScaled": "100000000",
"clientTriggerId": "twap-btc-1",
"twap": {
"side": "BUY",
"durationMs": "600000",
"sliceIntervalMs": "60000",
"marketIoc": { "maxSlippageBps": 50 }
}
}
}Errors
| Stable code | Cause |
|---|---|
TWAP_DURATION_INVALID | duration_ms is below 1000 |
TWAP_INTERVAL_INVALID | slice_interval_ms is below 100 or above duration_ms |
MAX_SLIPPAGE_INVALID | A sell's slice cap is 10000 bps |
PRICE_TICK_SIZE | The limit price is not on the Pair's tick size |
MARKET_TWAP_LIMIT_EXCEEDED | The account already runs 20 TWAPs on the Pair |
STEP_SIZE, MIN_QUANTITY, MIN_NOTIONAL | The first slice could never pass the Pair's order limits |
TRIGGER_NOT_MODIFIABLE | A pause, resume, or modify was sent for a TWAP |
CHILD_QTY_EXCEEDS_REMAINING | A slice edit would leave more than the TWAP's remaining quantity |
See Error codes for HTTP and ConnectRPC statuses.