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:
| Flag | Behavior |
|---|---|
post-only | Rejects if the order would take liquidity. |
reduce-only | Rejects if the order would increase position size in either direction. |
IOC | Immediate-or-cancel. Fills available quantity, cancels remainder. Never enters the book. |
FOK | Fill-or-kill. Rejects if the entire quantity cannot fill immediately. |
GTC | Good-til-cancel. Default time in force for limit orders. |
GTT | Good-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:
| Mode | Behavior |
|---|---|
cancel-newest | Incoming order cancelled. |
cancel-oldest | Resting order cancelled. |
cancel-both | Both 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:
- Healthy stays healthy. A fill must not cause a
HEALTHYaccount to becomeAT_RISKorLIQUIDATABLE. - Unhealthy improves. A fill on an account already in
AT_RISKmust 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
| State | Type | Description |
|---|---|---|
PENDING | Intermediate | Order has been received and is being validated. Pre-trade and risk checks have not completed. |
UNTRIGGERED | Intermediate | Conditional order (SL/TP/Stop) accepted but trigger price has not been reached. |
OPEN | Intermediate | Order is resting on the book. May have zero fills. |
PARTIALLY_FILLED | Intermediate | Order has accumulated fills but is still working with remainder. |
FILLED | Terminal | Order is fully filled. |
CANCELED | Terminal | Order was cancelled by the user or by system action before reaching FILLED. May have partial fills. |
REJECTED | Terminal | Order failed a pre-trade or risk check before entering the book. Has zero fills. |
EXPIRED | Terminal | Order'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 = IOCnever entersOPEN. - An order with
time_in_force = FOKis only everPENDING → FILLEDorPENDING → REJECTED. - An order with
post_only = trueisREJECTEDif it would cross at the time of placement. - A
reduce_onlyorder isREJECTEDif filling it would increase position size in either direction. - An order in
OPENorPARTIALLY_FILLEDmay transition only toPARTIALLY_FILLED,FILLED,CANCELED, orEXPIRED. - 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.
| # | Check | Rejection Code |
|---|---|---|
| 1 | Authentication and signature valid | ERR_AUTH |
| 2 | API key has trade scope | ERR_FORBIDDEN |
| 3 | Symbol exists and is listed | ERR_INVALID_SYMBOL |
| 4 | System and market state allow the order type | ERR_MARKET_STATE |
| 5 | Price within tick size; non-positive disallowed | ERR_INVALID_PRICE |
| 6 | Quantity within lot size; non-positive disallowed | ERR_INVALID_SIZE |
| 7 | Order would not breach per-account or per-market position limit | ERR_POSITION_LIMIT |
| 8 | Price within fat-finger band relative to mark | ERR_FAT_FINGER |
| 9 | If post_only, would not cross | ERR_POST_ONLY_CROSS |
| 10 | If reduce_only, would not increase position | ERR_REDUCE_ONLY_INCREASES |
| 11 | Account has sufficient initial and order margin | ERR_INSUFFICIENT_MARGIN |
| 12 | Account is not LIQUIDATABLE | ERR_ACCOUNT_LIQUIDATABLE |
| 13 | If account is AT_RISK, order satisfies the AT_RISK constraint (Section 7.3.1) | ERR_AT_RISK_INCREASE |
| 14 | Order does not breach self-trade prevention | ERR_SELF_TRADE |
| 15 | Account is within rate limits | ERR_RATE_LIMITED |
| 16 | Client order ID is not a duplicate | ERR_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
| Code | Meaning |
|---|---|
ERR_AUTH | Authentication failure or invalid signature. |
ERR_FORBIDDEN | API key lacks required scope. |
ERR_INVALID_SYMBOL | Symbol does not exist or is delisted. |
ERR_MARKET_STATE | Market state does not accept this order type. |
ERR_SYSTEM_STATE | System state does not accept this order. |
ERR_INVALID_PRICE | Price violates tick size or is non-positive. |
ERR_INVALID_SIZE | Quantity violates lot size or is non-positive. |
ERR_POSITION_LIMIT | Order would breach per-account or per-market position limit. |
ERR_FAT_FINGER | Price outside the fat-finger band. |
ERR_POST_ONLY_CROSS | Post-only order would take liquidity. |
ERR_REDUCE_ONLY_INCREASES | Reduce-only order would increase position size. |
ERR_INSUFFICIENT_MARGIN | Account lacks initial or order margin to support this order. |
ERR_ACCOUNT_LIQUIDATABLE | Account is in LIQUIDATABLE state; only the Liquidation Engine may operate. |
ERR_AT_RISK_INCREASE | Account is AT_RISK; order would not improve health. |
ERR_SELF_TRADE | Order would cross another order from the same account. |
ERR_RATE_LIMITED | Request rate limit exceeded. |
ERR_DUPLICATE_CLIENT_ORDER_ID | Client-supplied order ID already in use. |
ERR_ORDER_NOT_FOUND | Cancel or modify references an unknown order. |
ERR_ALREADY_TERMINAL | Cancel or modify references an order already in a terminal state. |
ERR_FOK_CANNOT_FILL | FOK order cannot be fully filled at submission. |
ERR_INTERNAL | Internal 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:
| Modification | Queue Position | Notes |
|---|---|---|
| Decrease quantity | Preserved | Reduce-quantity does not lose priority. |
| Increase quantity | Lost (treated as cancel + new) | New time priority assigned to the increased portion. |
| Change price | Lost | New time priority assigned. |
| Change post-only or reduce-only flags | Lost | New 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.

