Appearance
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 registeredState 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
- Pessimistic check (before PriceAlert RPC): Uses position entry prices as proxy, assuming 0 unrealized PnL. Fast, no network call.
- 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
- OrderExecutionManager — trade execution
- PendingOrderManager — pending market/close orders
- BalanceManager — margin calculations