A TWAP order divides a trade into smaller orders across a time window. Submit one parent order; Monaco schedules and tracks the child orders on the server.Spreading execution trades immediacy for an average across the window. The full quantity may not fill. Execution stops at the window end and reports any shortfall.
Choose An Execution Style
Taker is the default. Passive can produce both maker and taker fills. Its maker fills use the pair’s maker fee and carry no application fee; deadline sweeps and crossing takes incur application taker fees.
Set The Order
All timestamps use UTC with a
Z suffix. At most 25 parents can be open per user, including future-start orders. Delegated-agent sessions cannot create, cancel, fetch, or list TWAP parents in v1.The schedule is capped at 2,880 slices. Longer windows stretch the interval: a 30-day taker window uses roughly 15-minute intervals. Market minimums, maximums, and quantity steps also constrain the schedule.Price Protection For Passive Orders
The parent limit and slippage band apply to passive quotes as well as taker slices. If a new quote would be outside those bounds, it is not placed. If an older child is already resting, a refused reprice can leave it working at its old price.Set a limit if you need a hard price bound.
If no mark, usable book reference, or recent trade reference is available, the passive quote’s band check is skipped. The parent limit price is then its only bound.
Inspect both parent progress and open orders. Cancelling the parent removes a resting passive child; cancelling the child directly is not supported.If no mark, usable book reference, or recent trade reference is available, the passive quote’s band check is skipped. The parent limit price is then its only bound.
If Execution Falls Behind
Funds are checked per slice, not locked for the full parent upfront. A slice can be skipped because of insufficient funds or margin, unavailable liquidity, the limit price, or a market halt. A skipped or failed slice does not terminate the parent.Later slices try to catch up, normally capped at 3× the base size and never above the market maximum. A minimum-lot remainder exception is described in the full rules. After a gap, the schedule sends one catch-up slice and skips missed slots, rather than sending a burst.Illustrative outcomeRequested: 10 units · Executed: 7 · Shortfall: 3
If the window ends after 7 units fill, the parent is completed with a 3-unit shortfall. The remaining quantity is not traded after the deadline.
Monitor Or Stop The Order
The parent reports executed quantity, average fill price, progress, placed and skipped slices, and the next planned slice time. Completed means either all quantity executed or the window ended. Check executed quantity and shortfall as well as state.Cancel the parent to stop execution. Existing fills stand. A passive child is removed in the same step, with no final taker sweep.Cancel-all does not stop TWAP parents. Cancelling or replacing an individual child is rejected. Use the parent order as the control.
Start When A Price Is Reached
A conditional TWAP waits for a reference price to reach or cross a chosen level. It takes a duration; its execution window starts when triggered. An already-met trigger is rejected at creation. An expiry limits the waiting period and defaults to, and is capped at, 30 days from creation.Margin triggers use the oracle mark, with a venue-reference fallback while waiting; spot triggers use the venue reference. Polling can miss a crossing that reverses between observations. The reference retains exact activation, timestamp, collateral, and expiry semantics.Once the parent completes or is cancelled, a private execution-quality report compares its average fill price, before fees, with arrival and window benchmarks. Benchmarks without sufficient data are omitted.TWAP Execution And Integration
TWAP Execution And Integration
A TWAP (time-weighted average price) order executes a large order as a schedule of smaller child orders spread across a time window, so the realized price tracks the market’s average over the window rather than the price at a single moment. Monaco runs TWAP execution server-side: you submit one parent order with a total quantity and a window, and the matching engine plans, places, and tracks the child orders (“slices”) for you. Filling a large order in one print pays for immediacy with market impact; spreading the same quantity across a window trades immediacy for a better average.
TWAP Mechanics at a Glance
- One parent order defines the total quantity, side, market, and execution window; the engine derives a slice schedule from it (roughly one slice every 5 seconds, up to 2,880 slices — windows past 4 hours stretch the interval so the cap holds; a maximum-length 30-day window slices roughly every 15 minutes). The default
TAKERstyle targets one slice every 5 seconds; aPASSIVEparent targets one every 60 seconds under the same cap. - Each slice is placed as a market child order with immediate-or-cancel semantics, executing inside a protective price band (100 bps by default). An optional parent limit price additionally caps every child. A
PASSIVEparent instead rests each slice as a post-only maker child at the touch and takes only the remainder at the slot deadline — see Passive Execution. - Slice sizes and timing are randomized by default (±30% size, ±30% timing) using a recorded seed, so the schedule is reproducible for audit but unpredictable to the market.
- Every slice passes normal risk and balance checks individually. A skipped or failed slice never cancels the parent — the deficit flows into later slices via catch-up.
- Execution stops hard at the window end. Any unfilled remainder is reported as shortfall; it is never traded past the window.
Parameters
A TWAP parent order takes:- Market, side, and quantity: the trading pair, BUY or SELL, and the total quantity to execute. The quantity must be at least twice the pair’s minimum order size so it can be sliced, and no larger than the window’s slice budget can carry with every child at or below the pair’s maximum order size — an oversized parent is rejected at creation instead of accepted into a shortfall it could never avoid.
- Execution window: exactly one of a duration or an end time. Duration runs from launch; an end time can be paired with an optional start time (default: now) to schedule the window in advance. The window must be between 1 minute and 30 days by default. Timestamps are UTC only — RFC 3339 with a
Zsuffix (e.g.2026-08-01T10:00:00Z); a non-UTC offset is rejected rather than converted, and every timestamp the API and WebSocket return is UTC. - Limit price (optional): a parent-level price cap. BUY children never execute above it, SELL children never below it. When the market trades through the limit, slices are skipped rather than executed at a worse price. Omitted, only the slippage band protects child orders.
- Slippage tolerance (optional): the per-slice protective price band in basis points around the reference price. The default is 100 bps (1%) and the maximum is 500 bps (5%) — deliberately tighter than the standard market-order protection band, because a TWAP slice should never sweep deep into the book.
- Randomize (optional, default on): applies seeded randomization to slice sizes and timing. Turned off, the schedule is exactly uniform.
- Execution style (optional, default
TAKER): how each slice reaches the book.TAKERfires a banded market child at every slot — the behaviour described under Per-Slice Execution.PASSIVErests a post-only child at the touch and sweeps only what the slot deadline forces, trading immediacy for maker fees; see Passive Execution. - Trading mode: SPOT or MARGIN. Margin parents also take the usual margin context (margin account, risk bucket or margin mode, leverage) and support reduce-only, which child orders inherit and clamp to the live position. The risk bucket does not have to exist yet, and nothing moves at creation. Like a first-use market order, a missing (or closed) isolated bucket is created at zero by the parent’s first slice, and a missing cross bucket is provisioned empty at creation. Every slice draws what it needs from the margin account’s free collateral as it reserves, so the whole parent’s margin is never earmarked ahead of need; a slice the account cannot cover (the mark has moved, or the collateral is committed elsewhere) is skipped and reported, never fatal to the parent. A reduce-only parent still requires an existing bucket, since there is no position to reduce without one.
- Client TWAP ID (optional): a client-assigned idempotency key, unique per user and application — the server’s enforced uniqueness scope. Submitting a duplicate returns HTTP 409 rather than creating a second parent, so a retried request cannot double an execution. Keys are single-use forever within that scope: a key stays reserved even after its parent completes or is cancelled.
The Slice Schedule
At creation, the engine derives a slice plan from the window and quantity: a slice count targeting roughly one slice every 5 seconds — 60 seconds for aPASSIVE parent, whose children have to rest long enough to earn queue position — capped at 2,880 slices, and clamped so that every slice — even after size randomization — stays between the pair’s minimum and maximum order sizes. Slice sizes are floored to the pair’s quantity step, and the final slice is the exact remaining quantity (itself always a placeable size), so the plan always sums exactly to the requested quantity and can never over-allocate or leave an unplaceable tail. A window or quantity that cannot fit at least two slices is rejected at creation, as is a quantity too large for the window’s slice budget to carry.With randomization on, each slice’s size is jittered within ±30% and its fire time within ±30% of the base interval. The jitter is derived from a random seed minted at creation and recorded durably: the schedule never depends on wall-clock randomness, so it can be re-derived exactly for verification and audit, while remaining unpredictable to anyone watching the book. Uniform, evenly spaced slices of equal size are easy to detect and trade ahead of; jitter removes that signature.Per-Slice Execution
This section describes the defaultTAKER execution style; Passive Execution covers PASSIVE. Each slice is placed as a market child order with immediate-or-cancel semantics — child orders execute immediately against the book inside their protective band and never rest. There is no upfront lock of the full parent notional. Instead, every slice individually passes the same admission checks as a standalone order at the moment it fires:- Spot slices check the available balance for that slice’s quantity.
- Margin slices pass normal per-slice margin admission against the risk bucket the parent snapshotted at creation (provisioned then if it did not exist), topping it up from the margin account’s unallocated collateral when a slice needs more than the bucket holds.
CANCEL_MAKER the owner’s resting order is cancelled — surfacing as an OrderCancelled with terminalReason SELF_TRADE_PREVENTION on the order channels, not as a skip on the parent — and the slice proceeds.A slice whose only in-band depth is the owner’s own order is mode-dependent. Under CANCEL_MAKER or SKIP the child sees no executable third-party depth — liquidity validation excludes your own resting orders — so it is refused before sequencing and the parent records an ordinary insufficient-liquidity skip; nothing of yours is cancelled. Under CANCEL_TAKER or CANCEL_BOTH the child is instead CANCELLED by the dispatch itself and carries no unfilled reason, so validation accepts it, it is sequenced, and the parent counts it as placed — with CANCEL_BOTH also cancelling the maker. And under CANCEL_TAKER or CANCEL_BOTH a child stopped at the first self-touch comes back CANCELLED; TWAP counts any non-REJECTED child as placed, so that slice increments slicesPlaced rather than counting as a skip. It is not necessarily a zero-fill slice either — fills made against third parties before the first self-touch stand and are recorded as that slice’s quantity; only a self-touch before any fill leaves it at zero. Monitoring that inferred self-trade from slice_skipped should read the order channels instead. Skipped and failed slices are counted on the parent and nothing more — they never terminate it. There is no consecutive-failure kill: the schedule keeps attempting until the window ends, and the unexecuted quantity flows into catch-up.Passive Execution
SetexecutionStyle to PASSIVE and each slice is worked as a maker order instead of taken. At the start of a slot the engine rests a post-only LIMIT GTC child at the same-side touch — the best bid for a BUY, the best ask for a SELL — and leaves it there to earn queue position. As the touch moves, the child chases it with cancel/replace reprices, throttled to at most one attempt per second so a fast book cannot turn into a reprice storm. At the slot’s deadline, whatever the child has not earned is swept as a banded MARKET child, exactly like a TAKER slice. The parent ends up maker wherever the book allowed it and taker only for what the window forced.The two price bounds are the same ones a TAKER slice respects, and the engine does not post outside either: the parent’s own limitPrice, and the parent’s slippageToleranceBps band around the protective reference price — the oracle mark on a margin market when one is available, otherwise a robust book-derived venue price (from the best bid/ask, when the spread is not degenerate) falling back to the recent trade price, the median of the market’s last five trades. Spot parents always use that book-derived chain, since spot has no oracle mark. There is one exception worth knowing if you lean on the band for protection: when the engine can resolve no reference at all — a one-sided book on a pair with no trade history, so no mark, no trustworthy two-sided venue price and no recent trade price — the band check is skipped rather than failing closed, and limitPrice becomes the only bound on the resting quote. Set one on a passive parent if you need a hard ceiling in a market that thin.The engine does not clamp to the bound — when the same-side touch lies outside it the engine refuses to quote there, and what that leaves on the book depends on whether a child is already resting. With no child resting, nothing is posted: the slot stays open for the next attempt or the deadline sweep, since an off-market child would only sit there unfilled. With a child already resting, the reprice is refused before the cancel, so that child stays on the book at its old price — deliberately, because pulling it with nothing to put back is worse than leaving a stale quote. So a bounded passive parent in a market that has run away does not necessarily have zero resting exposure: it may still have its last in-bound child working, at a price the market has left behind. Read the parent’s slice counters and your open orders rather than assuming the book is clear — and stop it by cancelling the parent, since the child itself is not directly cancellable. If instead the passive target price would cross the book — the other side is already offering at or better than where the child would rest — resting would be pointless, so the placement executes immediately as a banded taker.Passive parents plan on a slower cadence: a 60-second target slice interval by default against the taker default of ~5 seconds, because a child that is repriced every few seconds never sits in the queue long enough to be filled. The 2,880-slice cap is unchanged, so a long window stretches its interval rather than adding slices. Both the cadence and the reprice throttle are operator-tunable.Cancellation is cancel-only. A PASSIVE parent may have one child resting when you cancel it. That child is removed in the same atomic step that terminalizes the parent: fills it already earned stand, the rest of the quantity is simply released, and nothing is left on the book. No final sweep is fired on a user cancel — cancelling means stop, not “finish what you can”.Fees follow each fill’s own side. A passive maker fill pays the pair’s maker fee and carries no application fee, because the application fee is a taker-side fee. Deadline sweeps and crossing takes pay application taker fees like any other taker execution. That is the whole point of the style: on a market where the maker fee is a rebate, a parent that rests most of its quantity can execute at a better all-in cost than the same parent sweeping every slot.On the twap_orders WebSocket channel, a passive parent adds three reasons to the usual lifecycle: passive_child_placed (a post-only child now rests at the touch), child_repriced (the reprice step ran), and passive_fill (maker fills on the resting child, folded into the parent). Deadline sweeps and crossing takes still arrive as slice_placed, so a client that only tracks slice counters keeps working unchanged.Two of those reasons are less literal than they look. child_repriced covers the cancel-only fallback as well: when the old child was cancelled and the repost did not happen, the same reason is emitted with nothing resting and the slot left open — so treat it as “the reprice step ran”. The frame cannot tell you which happened: the parent payload carries no active-child field, so resting-child status is not observable from this channel at all; follow the child on the ordinary order channels if you need it. And the maker fill that completes the parent arrives as completed, not passive_fill; there is no final passive_fill to wait for.Everything else is ordinary TWAP behaviour: the same slice plan, randomization, per-slice admission checks, catch-up sizing, hard stop, and shortfall reporting described above.Falling Behind and Catch-Up
The parent tracks a linear execution target across the window. When execution falls behind — skipped slices, a halted market, or an engine pause — the next slice is sized up to cover the deficit, capped at 3× the base slice size and never above the pair’s maximum order size. One exception to the 3× cap: if completing a slice would leave a remainder smaller than the pair’s minimum order size — too small for any later slice to ever place — the slice takes the whole remainder instead, exceeding the cap by less than one minimum lot (against a remainder above the pair’s maximum it backs off to leave exactly one placeable lot for the next slot rather than exceed the maximum). Randomization and slicing can therefore never strand an unfillable dust remainder; a TWAP that ends short did so because the market would not fill inside the protective band, never because of the schedule’s own arithmetic. After a gap, the schedule fires one catch-up slice and jumps past all missed slots; it never fires a burst of make-up orders. If the planned schedule is exhausted with quantity remaining, the engine keeps attempting slices at the base cadence until the window closes.The cap bounds the market impact of any single slice regardless of how far behind the parent is; a long outage is recovered gradually over the remaining window rather than dumped into the book at once.The Hard Stop
The window end is absolute. When it is reached with quantity outstanding, the parent completes with the shortfall available from the structured fields — the unfilled remainder is exactlytotalQuantity - executedQuantity — and summarized in the parent’s terminal reason (for example, ended with shortfall 1.5), which is human-readable text, not a parsing contract. Execution is never extended past the window to chase the remainder — a TWAP order’s contract is this quantity, inside this window, at protected prices, and the shortfall is the honest report of what that contract could not fill.Lifecycle
A TWAP parent moves through four states:- PENDING: created; no slices have fired yet (every parent is created PENDING, and a future start time keeps it there until the window opens). A conditional parent also waits here — indefinitely — until its trigger fires.
- ACTIVE: the window is open and the schedule is executing.
- COMPLETED: terminal. Either the full quantity executed, or the window ended and the remainder is reported as shortfall in the terminal reason.
- CANCELLED: terminal. The owner cancelled the parent; quantity already executed stands.
conditionalExpiry, the parent goes CANCELLED with the terminal reason trigger expired, having executed nothing. It leaves nothing behind: a first-use isolated risk bucket is only created by the first slice, so a parent that never sliced never created one.There is no separate FAILED state — individual slice failures are surfaced through the parent’s structured counters (slicesSkipped alongside slicesPlaced) and the WebSocket slice_skipped events, while the lifecycle state stays PENDING/ACTIVE until terminal COMPLETED or CANCELLED. Monitor the counters, not the state, for degraded execution. Cancellation is atomic and immediate: a TAKER parent’s children never rest on the book, so cancelling the parent stops the schedule with nothing left to unwind, and the parent’s fill-so-far is reported as its final execution. A PASSIVE parent can have one child resting, and cancellation removes it in that same atomic step — cancel-only: earned fills stand, no final sweep fires, and nothing is left behind either.The parent is the only control surface. A slice child is engine-owned machinery rather than an order you placed, so cancelling or replacing one directly is rejected and the message names the parent to act on instead. This is deliberate: a child cancelled on its own leaves the parent running, and the parent sweeps that slot as a taker at its deadline — so an order you believed cancelled comes back and pays taker fees. Cancel the parent and the resting child goes with it. A cancel-all sweep behaves the same way: the slice child is reported as a single refused item naming its parent, and every other order in the sweep still cancels. Cancel-all does not stop TWAP parents; only cancelling the parent does.Conditional TWAP: Price-Triggered Activation
A TWAP parent can be created to sit still and start trading only when the market reaches a price, instead of only at a wall-clock time. SupplytriggerPrice together with an explicit triggerDirection (ABOVE or BELOW) and the parent is created PENDING, watching rather than executing.- Reach or cross.
ABOVEactivates when the reference price is at or above the trigger;BELOWwhen it is at or below. No exact print is required, so a gap straight through the level activates. The condition is evaluated on the engine’s own polling cadence rather than on every market tick, so a price that crosses and reverts entirely between two observations is not seen — there is no high/low watermark. - The window floats. A conditional parent takes
durationSecondsonly;startTimeandendTimeare rejected, because the window is[trigger instant, trigger instant + durationSeconds]and cannot be known at creation. The slice count and interval are derived at creation, and the fire times re-anchor to the trigger instant when it arrives. Until then the create response’sfirstSliceAtandendTimeare provisional placeholders computed from[create, create + durationSeconds]; read the armed window back from the parent (startTime/endTime/triggeredAton the REST read model,startAt/endAton thetwap_ordersWebSocket payload) rather than scheduling on them. - Activation is one-way. Once the parent is trading the trigger is never re-evaluated, and
triggeredAtrecords when it fired. - Which price. Margin markets arm against the oracle mark, and a create is rejected while the market has not published one yet — a rejected create moves nothing, so retrying once a mark exists is cheap. Once the parent is waiting, activation falls back to the venue reference price (a book-derived median, then the median of the market’s last five trades) when the mark is unavailable, so a mark outage cannot leave a live trigger unwatched. Spot markets use the venue reference price throughout. On a one-sided spot book that means a move through the trigger is seen from its third trade at the new level, not its first.
- A trigger already met is refused. Creating a parent whose condition is already satisfied returns a 400 rather than starting immediately — omit
triggerPriceif immediate execution is what you want. conditionalExpirybounds the wait. An optional RFC 3339 UTC deadline (Zsuffix only). If the trigger has not fired by then the parent is CANCELLED with the terminal reasontrigger expired. It defaults to, and is capped at, the maximum window (30 days) from creation, and fires on the clock alone — no price observation is needed, so a halted or unpriceable market cannot leave a parent waiting past its deadline.
Limits
- The execution window must be between 1 minute and 30 days by default.
- Each user can have at most 25 open TWAP parents (scheduled or executing) at a time — a future-start parent counts against the cap from creation.
- Both spot and margin markets are supported.
- Delegated-agent sessions cannot use TWAP orders in v1: creating, cancelling, fetching, and listing parents are all rejected.
- TWAP parent orders are private to their owner; they are never visible to other participants.
Monitoring
The parent order is the single view of progress: it reports executed quantity, executed notional, the volume-weighted average fill price, progress as a fraction of the total, slices placed and skipped, the next planned slice time, and the terminal reason once finished. TWAP parents can be fetched individually or listed with state and market filters.Parents are not rows of the ordinary order listing (GET /api/v1/orders), which carries book orders only. Read them from the dedicated TWAP list (GET /api/v1/orders/twap), which filters by state and market, or fetch one by ID.For live updates, the WebSocket API carries a dedicated authenticated twap_orders channel (optionally filtered to one market with twap_orders:{tradingPairId}). It delivers the full parent snapshot on every mutation — creation, each placed or skipped slice, cancellation, completion, for a PASSIVE parent passive_child_placed, child_repriced and passive_fill, and for a conditional parent triggered (the trigger fired and the window armed) and trigger_expired (the deadline passed with the trigger unfired) — tagged with the reason for the update, so a client can render live progress without polling. The formatted detail for an expiry (trigger expired) rides terminalReason, not the reason field. Like the parents themselves, the channel is private: only the owner receives events.Each child order is a regular order — fills flow through the standard authenticated order channels on the WebSocket API, and child orders record the parent’s ID in their parentOrderId field. The same field rides every order frame of a child and its orders snapshot row, so a parent’s children can be tied together from the socket alone. Children are not rows of the ordinary order listing (GET /api/v1/orders), resting or finished: they belong to their parent.To read one parent’s children — its slices — in one place, list them with sdk.trading.listTwapOrderSlices (GET /api/v1/orders/twap/{twapOrderId}/slices; gRPC OrdersService.ListTwapOrderSlices). Each slice comes back in the ordinary order shape, resting and finished alike, oldest first (orderBy=DESC for newest first), in cursor pages of up to 1000. A PASSIVE parent’s reprices and deadline sweeps are child orders too, so it can have more slices than slicesPlaced. Like the parent, the slice list is owner-private and rejects delegated-agent sessions.Execution Quality (TCA)
Once a parent is terminal — COMPLETED or CANCELLED — a post-execution transaction cost analysis report scores how well the schedule executed. The report compares the realized average fill price (gross of fees, so it compares cleanly against price benchmarks) with two references over the parent’s active window, from its start to its actual terminal time (never the planned end — a cancelled parent stopped trading at cancellation): the arrival price, the reference price as the window opened, and the window-TWAP benchmark, the average reference price across the window’s sealed minute buckets. Both references come from a single price series named in the report — the 1-minute risk mark for marked pairs, falling back to 1-minute OHLCV closes — and both scores are signed basis points where positive always means the execution beat the reference: a BUY that filled below it, a SELL that filled above it.The analytics are honest or absent: when nothing executed, no reference series adequately covered the window, or the window was too short to seal a single minute bucket, the affected fields are omitted from the report rather than filled with a defensible-looking number. The structural fields — quantities executed and short, the window bounds, market and side — are always present. Fetch the report withsdk.trading.getTwapOrderTca (GET /api/v1/orders/twap/{twapOrderId}/tca; gRPC OrdersService.GetTwapOrderTca); like the rest of the TWAP surface it is owner-private and rejects delegated-agent sessions.For order payloads and order management behavior, use Order Management.
