Skip to content

LimitOrderManager

Manages limit order lifecycle: creation, cancellation, update, execution marking, and margin reservation.

High-Level Design

Core Responsibility

LimitOrderManager owns the limit order state machine and margin reservation. It does not execute trades itself — it delegates execution to OrderExecutionManager and alert registration to PriceAlert DO via AlertManager.

Data Flow: Order Creation

User RPC (handleOpenLimitOrderRPC)

  ├─ 1. Validate input (InputValidator)
  ├─ 2. Validate leverage (InputValidator + existing position check)
  ├─ 3. Check margin availability (BalanceManager)
  │     └─ Uses pessimistic prices (entry prices, assumes 0 unrealized PnL)

  ├─ 4. Register with PriceAlert DO via registerLimitOrderWithPriceCheck()
  │     └─ Single RPC that atomically checks price + registers alerts

  └─ 5. Branch on PriceAlert response:

        ├─ shouldExecuteNow=true → Delegate to OrderExecutionManager (immediate market order)

        ├─ conditionAlertId only → Create ACTIVE order
        │   └─ Re-check margin with real prices before persisting

        └─ priceArrivalAlertId → Create PENDING_PRICE order
            └─ Price data not yet available; two alerts registered

State Machine

PENDING_PRICE ──(price arrives)──→ ACTIVE
     │                                │
     │                                ├──(price crosses target)──→ EXECUTED
     │                                ├──(user cancels)──────────→ CANCELLED
     │                                └──(execution fails)───────→ FAILED

     └──(bulk cancel only)───────→ CANCELLED
  • PENDING_PRICE: Created when price data is unavailable at order time. Holds both a condition alert and a price-arrival alert. Transitions to ACTIVE once price arrives. Single cancel (cancelLimitOrder) rejects non-ACTIVE orders — only bulk cancel handles PENDING_PRICE.
  • ACTIVE: Price data exists but target not yet met. Condition alert registered with PriceAlert DO.
  • EXECUTED / CANCELLED / FAILED: Terminal states. Cleaned up after 24h.

Margin Reservation

Margin is reserved at creation (sizeUsd / leverage) and implicitly released on terminal state — no explicit release call exists. getTotalReservedMargin() sums only ACTIVE + PENDING_PRICE orders, so terminal orders naturally stop contributing.

This "eventually correct" approach means margin is locked before execution, preventing overdraft even if execution is delayed.

Two-Phase Margin Check

  1. Pessimistic check (before PriceAlert RPC): Uses position entry prices as proxy, assuming 0 unrealized PnL. Fast, no network call.
  2. Real-price check (ACTIVE path only): After PriceAlert returns price data, re-checks margin with actual prices. Prevents creating an order that would immediately be under-margined.

The PENDING_PRICE path skips the second check because no price data exists yet.

Dual-Alert System for PENDING_PRICE Orders

When price data is unavailable at creation time, PriceAlert registers two alerts:

  • Price-arrival alert: Fires when the timestamp's price data becomes available → triggers transitionToActive()
  • Condition alert: Fires when price crosses the target → triggers execution

Both alerts are registered atomically via registerLimitOrderWithPriceCheck().

Update Flow

handleUpdateLimitOrderRPC supports changing target price, stop loss, and take profit on ACTIVE orders:

  • Target price change: Deregisters old alert → fetches current price → rejects if new target would trigger immediately → registers new alert
  • SL/TP change: Direct SQL update, no alert interaction

Cancel & Leverage Interactions

  • Single cancel: Deregisters alert → marks CANCELLED → broadcasts event → checks if symbol leverage should reset (via LeverageSettingsManager)
  • Bulk cancel: Single SQL UPDATE for efficiency, best-effort alert deregistration in parallel
  • Leverage update (updateLeverageForSymbol): When a position's leverage changes, all active orders for that symbol get their leverage and reserved margin recalculated. Returns margin delta for balance adjustment.

Edge Cases

  • Pessimistic margin check avoids needing real-time prices at creation time
  • Immediate execution when price condition already met avoids unnecessary alert creation
  • Update rejects immediate triggers — changing target price to a value already met returns an error instead of silently executing
  • Account ID scoping on all queries prevents cross-account operations
  • Bulk cancel uses single SQL UPDATE, broadcasts per-order WebSocket events
  • 24h cleanup on initialization removes terminal orders

See Also