pub async fn bulk_replace_order(
__arg0: State<AppState>,
__arg1: SonicJson<BulkReplaceOrderRequest>,
) -> Result<SonicJson<BulkReplaceOrderResponse>, ApiError>Expand description
Replace multiple orders using cancel-all-then-create-all ordering.
Per-leg semantics mirror PUT /order (cancel old + place new), but across a batch every leg’s cancel is enqueued before any create, so the engine cannot match a new leg against a sibling’s still-resting opposite. This eliminates the self-trade-prevention kill path that fired when the bulk dispatched independent ReplaceOrder commands. A per-leg response is returned in original index order; a leg whose cancel failed (already filled / not found) is skipped for the create phase and returned with the cancel rejection details. Not atomic across the batch: the gap between a leg’s cancel committing and its create landing is per-leg. See engineering-docs/docs/flows/order-lifecycle.mdx for tradeoffs vs. the single-command PUT /order path. Bulk replace orders.
Executes cancel-all-then-create-all ordering across the batch: every
leg’s cancel is enqueued before any create, so the engine is guaranteed
to process all cancels (removing every old leg from the book) before any
new order matches. Without this, the engine’s FIFO processing of
independent OrderAction::ReplaceOrder commands meant each command’s
phase-2 place could match against a sibling’s still-resting opposite
leg, triggering self-trade prevention and losing the new leg entirely.
The path is:
validate_replace_requestper leg (sig / auth / parse / precision).orchestrate_bulk_replacedispatches allCancelOrdercommands, awaits responses, then dispatchesCreateOrdercommands only for legs whose cancel actually reachedCanceled.- Per-leg results merge: prefer the create response; fall back to a cancel-only result when the cancel landed but the create didn’t.
Semantics vs. single-command ReplaceOrder: the PUT /order single-
replace path dispatches one OrderAction::ReplaceOrder engine command
that handles the cancel and place atomically within one engine tick and
one journal entry (src/rsm/unified_engine/order_routing.rs:process_replace_order).
The bulk path, by contrast, issues separate CancelOrder and CreateOrder
commands (2N journal entries per bulk of N), so there is a per-leg gap
between a leg’s cancel committing and its create landing. Real-world MMs
re-quote every cycle, so a missed create is recovered on the next tick.
The PUT /order single-replace endpoint is unchanged.