OriginDOCS

Trading

Order Types & Flags

Core Orders

Origin supports limit, market, stop-market, and stop-limit orders as the primary execution types.

  • Limit. Rests on the book until matched, cancelled, or expired.
  • Market. Executes immediately against available liquidity up to configured slippage protection.
  • Stop-Market and Stop-Limit. Activate when a trigger price is reached, after which the order behaves as the underlying type.

Conditional Orders

  • Take-Profit (TP) and Stop-Loss (SL). Attaches to an existing position or limit order. Triggers on mark price.
  • Trailing Stop. Trigger price adjusts dynamically as the market moves favorably. Triggers on mark price.

Conditional orders are tracked off-chain by the matching engine and only consume on-chain settlement bandwidth when they trigger and fill. Trigger price comparisons always use mark price (not last-trade price) to prevent wick-based triggers.

Trigger Conditions

For sell-side stop-loss and buy-side take-profit:

mark_price ≤ trigger_price

For buy-side stop-loss and sell-side take-profit:

mark_price ≥ trigger_price

Algorithmic Orders

  • TWAP. Slices a parent order into time-distributed child orders over a user-specified window. Each child order is a market order sized as (total_remaining / remaining_intervals), posted on a fixed cadence (default 30 seconds).
  • Scaled. Distributes liquidity across a price range with configurable step size.
  • Iceberg. Displays only a portion of total quantity to the book; refreshes from a hidden reserve as the visible portion fills.

Order Flags

Every order supports the standard execution flags:

FlagBehavior
post-onlyRejects if the order would take liquidity.
reduce-onlyRejects if the order would increase position size in either direction.
IOCImmediate-or-cancel. Fills available quantity, cancels remainder. Never enters the book.
FOKFill-or-kill. Rejects if the entire quantity cannot fill immediately.
GTCGood-til-cancel. Default time in force for limit orders.
GTTGood-til-time. Carries an explicit expiry timestamp; auto-cancels when reached.

Matching Engine

Price-Time Priority

The order book uses strict price-time priority. At each price level, the order entered first is filled first. This favors latency-sensitive market makers and produces predictable fill behavior compared to pro-rata or hybrid models.

When a taker order arrives, the engine matches against the highest-priority order on the opposite side at the maker's price. Matching continues until the taker is fully filled, the taker's limit price is exceeded, or no crossing orders remain. Any unfilled remainder of a taker limit order rests on the book at the taker's limit price, taking new time priority.

Self-Trade Prevention

Self-trade prevention (STP) protects users from matching their own orders across different sub-accounts or API keys. Supported modes:

ModeBehavior
cancel-newestIncoming order cancelled.
cancel-oldestResting order cancelled.
cancel-bothBoth cancelled.

STP mode is configurable per account and per order.

Cancel-on-Disconnect

API users can enable cancel-on-disconnect (COD), which automatically cancels all open orders for an API key when its WebSocket connection drops. COD is recommended for any market-making or systematic strategy to prevent stale orders from executing against unattended infrastructure.

Risk Invariants on Every Trade

The Matching Engine enforces two account-health invariants on every fill:

  1. Healthy stays healthy. A fill must not cause a HEALTHY account to become AT_RISK or LIQUIDATABLE.
  2. Unhealthy improves. A fill on an account already in AT_RISK must improve the account's health (i.e., satisfy the constraint in Section 7.3.1).

If a prospective fill would violate either invariant, the Matching Engine auto-cancels the offending resting order before the fill is committed. This guarantees that the order book represents only orders that, if filled, leave their accounts in a valid state.

Order Lifecycle State Machine

Overview

Every order placed on Origin moves through a defined sequence of states from submission to terminal disposition. The state machine is observable in real time over WebSocket and queryable over REST, and is identical for all order types: only the transitions taken differ.

The order lifecycle event stream is the canonical client-side source of truth (Section 9.2). Clients should not infer order state from order book snapshots or fills alone.

Order States

StateTypeDescription
PENDINGIntermediateOrder has been received and is being validated. Pre-trade and risk checks have not completed.
UNTRIGGEREDIntermediateConditional order (SL/TP/Stop) accepted but trigger price has not been reached.
OPENIntermediateOrder is resting on the book. May have zero fills.
PARTIALLY_FILLEDIntermediateOrder has accumulated fills but is still working with remainder.
FILLEDTerminalOrder is fully filled.
CANCELEDTerminalOrder was cancelled by the user or by system action before reaching FILLED. May have partial fills.
REJECTEDTerminalOrder failed a pre-trade or risk check before entering the book. Has zero fills.
EXPIREDTerminalOrder's time-in-force window passed before completion. May have partial fills.

Terminal states are final: an order in a terminal state cannot transition further. cumulative_fill_qty and average_fill_price are tracked separately from state and persist on terminal records.

State Transitions

The valid transitions are:

                                  ┌────► REJECTED
                                  │
   ┌─► PENDING ──────────────────┤
   │                              ├────► OPEN ────► PARTIALLY_FILLED ────► FILLED
   │                              │       │                  │
   │                              │       ▼                  ▼
   │                              │     CANCELED         CANCELED
SUBMIT                            │     EXPIRED          EXPIRED
   │                              │
   │                              ├────► FILLED                        (taker fully filled)
   │                              │
   │                              ├────► PARTIALLY_FILLED ────► CANCELED  (IOC partial; no OPEN state)
   │                              │
   │                              └────► UNTRIGGERED ──┬────► OPEN          (limit-style trigger)
   │                                                    ├────► FILLED       (market-style trigger)
   │                                                    ├────► CANCELED
   │                                                    └────► EXPIRED

Invariants on transitions:

  • An order with time_in_force = IOC never enters OPEN.
  • An order with time_in_force = FOK is only ever PENDING → FILLED or PENDING → REJECTED.
  • An order with post_only = true is REJECTED if it would cross at the time of placement.
  • A reduce_only order is REJECTED if filling it would increase position size in either direction.
  • An order in OPEN or PARTIALLY_FILLED may transition only to PARTIALLY_FILLED, FILLED, CANCELED, or EXPIRED.
  • Once an order reaches a terminal state, no further events for it are emitted.

Worked Example: Limit Order Lifecycle

Account places a limit order: BUY 1 BTC @ $80,000, GTC, post-only.

T+0ms      Client signs and submits order via WebSocket.
T+1ms      State: PENDING. API Gateway runs pre-trade checks.
T+2ms      All checks pass. Risk Engine confirms order margin available.
T+3ms      Order entered onto book at price 80,000 with sequence number 4_812_337.
           State: OPEN.
T+8ms      Another account places a market sell of 1 BTC.
T+9ms      Match: 0.4 BTC fills at 80,000. State: PARTIALLY_FILLED.
           cumulative_fill_qty = 0.4.
T+12ms     Match: 0.6 BTC fills at 80,000. State: FILLED.
           cumulative_fill_qty = 1.0, average_fill_price = 80,000.
T+13ms     Settlement Submitter accumulates the fills into the next batch.
T+450ms    Batch committed on Arc; on-chain settlement event published.

The client observes the OPEN event at T+3, two fill events at T+9 and T+12, and an on-chain settlement event at T+450. Trading decisions are made on the WebSocket fill events; the settlement event is a finality confirmation.

Pre-Trade Validation & Rejection Codes

Validation Sequence

When an order is submitted, the API Gateway and Risk Engine apply the following checks in sequence. The first failed check determines the rejection reason; subsequent checks are not run.

#CheckRejection Code
1Authentication and signature validERR_AUTH
2API key has trade scopeERR_FORBIDDEN
3Symbol exists and is listedERR_INVALID_SYMBOL
4System and market state allow the order typeERR_MARKET_STATE
5Price within tick size; non-positive disallowedERR_INVALID_PRICE
6Quantity within lot size; non-positive disallowedERR_INVALID_SIZE
7Order would not breach per-account or per-market position limitERR_POSITION_LIMIT
8Price within fat-finger band relative to markERR_FAT_FINGER
9If post_only, would not crossERR_POST_ONLY_CROSS
10If reduce_only, would not increase positionERR_REDUCE_ONLY_INCREASES
11Account has sufficient initial and order marginERR_INSUFFICIENT_MARGIN
12Account is not LIQUIDATABLEERR_ACCOUNT_LIQUIDATABLE
13If account is AT_RISK, order satisfies the AT_RISK constraint (Section 7.3.1)ERR_AT_RISK_INCREASE
14Order does not breach self-trade preventionERR_SELF_TRADE
15Account is within rate limitsERR_RATE_LIMITED
16Client order ID is not a duplicateERR_DUPLICATE_CLIENT_ORDER_ID

Fat-Finger Bands

The fat-finger check is configured per market. For ask (sell) limit orders:

ask_min_price = max(mark_price, best_bid) × ( 1 - fat_finger_pct )

For bid (buy) limit orders:

bid_max_price = min(mark_price, best_ask) × ( 1 + fat_finger_pct )

Default fat_finger_pct = 5%, adjustable per market. If best_bid or best_ask does not exist (empty side), only the mark price is used. For SL/TP-limit orders, the check is applied against the trigger price instead of the mark price.

Rejection Code Reference

CodeMeaning
ERR_AUTHAuthentication failure or invalid signature.
ERR_FORBIDDENAPI key lacks required scope.
ERR_INVALID_SYMBOLSymbol does not exist or is delisted.
ERR_MARKET_STATEMarket state does not accept this order type.
ERR_SYSTEM_STATESystem state does not accept this order.
ERR_INVALID_PRICEPrice violates tick size or is non-positive.
ERR_INVALID_SIZEQuantity violates lot size or is non-positive.
ERR_POSITION_LIMITOrder would breach per-account or per-market position limit.
ERR_FAT_FINGERPrice outside the fat-finger band.
ERR_POST_ONLY_CROSSPost-only order would take liquidity.
ERR_REDUCE_ONLY_INCREASESReduce-only order would increase position size.
ERR_INSUFFICIENT_MARGINAccount lacks initial or order margin to support this order.
ERR_ACCOUNT_LIQUIDATABLEAccount is in LIQUIDATABLE state; only the Liquidation Engine may operate.
ERR_AT_RISK_INCREASEAccount is AT_RISK; order would not improve health.
ERR_SELF_TRADEOrder would cross another order from the same account.
ERR_RATE_LIMITEDRequest rate limit exceeded.
ERR_DUPLICATE_CLIENT_ORDER_IDClient-supplied order ID already in use.
ERR_ORDER_NOT_FOUNDCancel or modify references an unknown order.
ERR_ALREADY_TERMINALCancel or modify references an order already in a terminal state.
ERR_FOK_CANNOT_FILLFOK order cannot be fully filled at submission.
ERR_INTERNALInternal system error. Safe to retry with backoff.

Order Margin

Origin reserves Order Margin against each resting limit order. Order Margin is a check applied at order placement; it does not affect position margin and is not considered for liquidation decisions. Its purpose is to ensure that orders resting on the book represent positions the account can actually take if filled.

Calculation

For a new limit order:

order_value = limit_price × order_size

order_margin_same_side =
    order_value × initial_margin_rate

order_margin_opposite_side =
    max( 0, ( order_value - 2 × |position_notional| ) ) × initial_margin_rate

The "same side" formula applies when the new order is on the same side as the user's existing position (or when there is no existing position). The "opposite side" formula applies when the new order would reduce the existing position. Because reducing-side fills convert position margin into free collateral, the opposite-side reservation is smaller.

Available Order Margin

The total Order Margin available to the account is:

available_order_margin =
    account_equity
    - initial_margin_used_by_positions
    - Σ_other_orders max( order_margin_same_side, order_margin_opposite_side )

If a new order's required Order Margin would exceed available_order_margin, the order is REJECTED with ERR_INSUFFICIENT_MARGIN.

Release on Terminal State

Order Margin reserved for an order is released back to the account's available pool when the order reaches any terminal state. Partial fills convert proportional Order Margin into Position Margin at fill time; the remainder stays reserved until the remainder itself terminates.

Cancel & Modify

Cancel

A cancel request removes a working order from the book. Cancels are accepted in any non-terminal state (OPEN, PARTIALLY_FILLED, UNTRIGGERED).

A cancel may race with a fill. If the cancel arrives at the matching engine after the order has already begun filling, the cancel applies only to the unfilled remainder. Cumulative fill quantity is preserved on the terminal CANCELED record. If the cancel arrives after the order has fully filled, the cancel returns ERR_ALREADY_TERMINAL.

Modify

Origin supports order modification (modify-in-place) for limit orders in OPEN or PARTIALLY_FILLED state. The semantics depend on what is modified:

ModificationQueue PositionNotes
Decrease quantityPreservedReduce-quantity does not lose priority.
Increase quantityLost (treated as cancel + new)New time priority assigned to the increased portion.
Change priceLostNew time priority assigned.
Change post-only or reduce-only flagsLostNew time priority assigned.

Modify is delivered as a single atomic operation: from the client's perspective, either the modify succeeds and the new order parameters are reflected, or it fails and the original order continues unchanged. There is no observable intermediate state on a successful modify.

Idempotency

Order placement is idempotent on client_order_id. If a client retries a placement with the same client_order_id while the original is still in flight or already accepted, the gateway returns the existing order's current state rather than creating a duplicate. Once the original order reaches a terminal state and a configurable retention window (default 24 hours) elapses, the client_order_id becomes available for reuse.

Cancel and modify requests are idempotent on the order's server-assigned order_id. Repeated cancels of an already-cancelled order return ERR_ALREADY_TERMINAL, which clients should treat as success.