Order Routing — Message Reference¶
All order routing messages share the same 56-byte common header used by market data. The consumer reads the header, dispatches on msg_type, and casts the remaining bytes to the appropriate payload struct.
The provided header library (sorcery/ord_types.h) contains all POD struct definitions and enums. For ring mechanics (poll, has_gap, reset), see Wire protocol — Client library.
Order routing uses two SHM rings per stack-scoped routing session:
- Request ring (
/sorcery-{stack}-ord-req) — client writes, orderd reads - Response ring (
/sorcery-{stack}-ord-rsp) — orderd writes, client reads
Ring names are stack-scoped. A stack has one request stream and one response stream.
Both rings use the same SPMC ring protocol as market data. Each ring has a single producer.
For multi-strategy systems, introduce a fan-in execution gateway as the sole request-ring producer. Direct multi-writer access to the same request ring is out of contract.
Multi-strategy routing contract¶
The wire protocol has no strategy_id field. In multi-strategy deployments, routing is defined by external order_id ownership.
Required gateway behavior:
- Persist
order_id -> strategy ownerbefore publishing NEW. - Route order-scoped responses by
order_idownership. - Route
order_id = 0responses to reconciliation handling only. - On unknown non-zero
order_id, fail closed (pause venue submissions) and reconcile.
Consumer pattern¶
Pin one thread per ring pair. The response ring is the hot path — drain a batch of frames into process-local memory, then dispatch on msg_type. The request ring is the submission path — encode a request into the ring and flush.
#include <sorcery/ord_types.h> // Header, NewOrder, Fill, OrderAck, ...
#include <sorcery/ring.h> // Ring, Consumer, Producer, DrainBuffer
#include <sorcery/metadata.h> // MetadataStore — instrument lookup + price conversion
// Open both rings
auto rsp_ring = sorcery::Ring::open("/sorcery-master-ord-rsp");
auto req_ring = sorcery::Ring::open("/sorcery-master-ord-req");
sorcery::Consumer consumer{rsp_ring};
sorcery::Producer producer{req_ring};
sorcery::DrainBuffer buf;
while (running) {
// Drain response ring — same pattern as market data
for (auto& frame : consumer.drain(buf, K)) {
if (frame.is_gap()) {
// Ring overflow — reconcile all order state via queries
reconcile_all_orders();
continue;
}
auto* hdr = frame.header();
auto* body = frame.body();
switch (hdr->msg_type) {
case sorcery::ord::ORDER_ACK:
on_order_ack(*hdr, *reinterpret_cast<const sorcery::ord::OrderAckMsg*>(body));
break;
case sorcery::ord::FILL:
on_fill(*hdr, *reinterpret_cast<const sorcery::ord::FillMsg*>(body));
break;
case sorcery::ord::ORDER_REJECT:
on_order_reject(*hdr, *reinterpret_cast<const sorcery::ord::OrderRejectMsg*>(body));
break;
case sorcery::ord::MODIFY_ACK:
on_modify_ack(*hdr, *reinterpret_cast<const sorcery::ord::ModifyAckMsg*>(body));
break;
case sorcery::ord::CANCEL_ACK:
on_cancel_ack(*hdr, *reinterpret_cast<const sorcery::ord::CancelAckMsg*>(body));
break;
case sorcery::ord::BALANCES:
on_balances(*hdr, sorcery::ord::BalancesView{body});
break;
// ... remaining types
}
}
}
Fixed-size payloads (OrderAck, OrderReject, ModifyAck, ModifyReject, CancelAck, CancelReject, CancelAllAck, Fill, OrderStatus, Status) can be cast directly. Variable-size payloads (Balances, Orders, Positions) are wrapped by view types that compute array offsets and return typed std::spans.
Submitting requests¶
// Submit a new limit order
sorcery::ord::NewOrder order{};
order.order_id = next_order_id();
order.px = 500000; // price in ticks
order.qty = 100; // quantity in steps
order.side = sorcery::ord::BUY;
order.tif = sorcery::ord::GTC;
order.flags = 0;
auto out = producer.get_buffer(sizeof(sorcery::Header) + sizeof(order));
encode_header(out, inst_id, sorcery::ord::NEW, venue);
std::memcpy(out.data() + sizeof(sorcery::Header), &order, sizeof(order));
producer.flush();
Price and quantity conversion¶
Same as market data. Prices and quantities on the wire are integers in tick/step units. Convert using instrument metadata (see Metadata):
sorcery::MetadataStore store("/sorcery-master-metadata");
auto* inst = store.find_instrument(hdr->inst_id);
double price = inst->to_price(fill.fill_px); // ticks → real price
double qty = inst->to_qty(fill.fill_qty); // steps → real quantity
Enums¶
msg_type — request ring (u8)¶
Client → orderd. Valid on the request ring only.
| Value | Name | Payload size |
|---|---|---|
| 1 | NEW |
32 bytes (fixed) |
| 2 | CANCEL |
8 bytes (fixed) |
| 3 | MODIFY |
16 bytes (fixed) |
| 4 | CANCEL_ALL |
0 bytes |
| 5 | QUERY_BALANCES |
0 bytes |
| 6 | QUERY_ORDERS |
0 bytes |
| 7 | QUERY_ORDER |
8 bytes (fixed) |
| 8 | QUERY_POSITIONS |
0 bytes |
msg_type — response ring (u8)¶
orderd → client. Valid on the response ring only.
| Value | Name | Payload size |
|---|---|---|
| 1 | ORDER_ACK |
48 bytes (fixed) |
| 2 | ORDER_REJECT |
16 bytes (fixed) |
| 3 | MODIFY_ACK |
48 bytes (fixed) |
| 4 | MODIFY_REJECT |
32 bytes (fixed) |
| 5 | CANCEL_ACK |
24 bytes (fixed) |
| 6 | CANCEL_REJECT |
24 bytes (fixed) |
| 7 | CANCEL_ALL_ACK |
12 bytes (fixed) |
| 8 | FILL |
80 bytes (fixed) |
| 9 | BALANCES |
variable |
| 10 | ORDERS |
variable |
| 11 | ORDER_STATUS |
64 bytes (fixed) |
| 12 | STATUS |
32 bytes (fixed) |
| 13 | POSITIONS |
variable |
order_state (u8)¶
| Value | Name | Terminal | Meaning |
|---|---|---|---|
| 0 | (reserved) | — | Invalid; zero-initialized memory. Consumers MUST treat as error. |
| 1 | PENDING_NEW |
no | Sent to exchange, awaiting ack |
| 2 | LIVE |
no | Resting on book, no in-flight mutation |
| 3 | PENDING_MODIFY |
no | Price modify sent, awaiting response |
| 4 | PENDING_CXL |
no | Cancel sent, awaiting response |
| 5 | FILLED |
yes | Fully filled |
| 6 | REJECTED |
yes | Never accepted by exchange |
| 7 | CANCELED |
yes | Cancelled after acceptance |
State tracks the mutation lifecycle, not fill progress. Whether an order has partial fills is determined by filled_qty > 0, not by the state value. See Integration guide — State machine for the full transition table.
side (u8)¶
| Value | Name |
|---|---|
| 1 | BUY |
| 2 | SELL |
Numeric values match market data BID=1, ASK=2 intentionally. BUY = bidder, SELL = asker.
tif (u8)¶
| Value | Name | Meaning |
|---|---|---|
| 1 | GTC |
Good-til-canceled |
| 2 | IOC |
Immediate-or-cancel |
| 3 | FOK |
Fill-or-kill |
order_flags (u8, bitfield)¶
Used in the NEW request payload's flags field.
| Bit | Name | Meaning |
|---|---|---|
| 0 | POST_ONLY |
Reject if would cross as taker |
| 1 | REDUCE_ONLY |
Only reduce existing position |
reject_reason (u8)¶
| Value | Name | Meaning | Source | Recoverable |
|---|---|---|---|---|
| 0 | UNKNOWN |
Unmapped exchange error | exchange | no |
| 1 | INTERNAL |
orderd internal error | local | no |
| 2 | EXCHANGE |
Generic exchange rejection | exchange | no |
| 3 | INSUFFICIENT_MARGIN |
Not enough margin/capital | exchange | yes |
| 4 | RATE_LIMITED |
Exchange rate limit hit | exchange | yes |
| 5 | INVALID_PRICE |
Price fails validation (tick size, bounds) | both | no |
| 6 | INVALID_QTY |
Quantity fails validation (step size, min/max) | both | no |
| 7 | INVALID_INSTRUMENT |
Instrument not active or not found | local | no |
| 8 | POST_ONLY |
Would cross as taker | exchange | yes |
| 9 | SELF_TRADE |
Self-trade prevention triggered | exchange | yes |
| 10 | REDUCE_ONLY |
Would increase position | both | yes |
| 11 | TOO_LATE |
Order already filled/cancelled | exchange | no |
| 12 | MARKET_CLOSED |
Market not open for trading | exchange | yes |
| 13 | DUPLICATE_ORDER |
order_id already in use | local | no |
| 14 | ORDER_NOT_FOUND |
order_id not found (cancel/modify) | local | no |
| 15 | MODIFY_IN_FLIGHT |
Another modify/cancel already pending | local | yes |
| 16 | QTY_MODIFY |
Qty modification not supported (cancel + new) | local | no |
| 17 | DISCONNECTED |
Not connected to exchange, or unresolved after reconnect reconciliation | both | yes |
Source: local = rejected by orderd before reaching the exchange (response has LOCAL flag set). exchange = rejected by the venue after transmission. both = may originate from orderd local validation, the exchange, or reconciliation synthesis.
DISCONNECTED may appear as a synthetic reconciliation ORDER_REJECT with RECONNECT set and LOCAL unset.
Recoverable means the same request may succeed if retried after the condition resolves. Non-recoverable means the request is fundamentally invalid.
response flags (u16, bitfield)¶
Used in the response ring header's flags field.
| Bit | Name | Meaning |
|---|---|---|
| 0 | LOCAL |
Rejected locally by orderd; never sent to exchange |
| 1 | RECONNECT |
Epoch changed; consumer MUST reconcile |
Common header¶
Both rings reuse the same 56-byte common header as market data. The header struct is identical — only the semantic interpretation of certain fields differs by ring direction.
There is no strategy_id/app_id field in this header. Strategy-level routing must be handled outside the wire protocol (typically by order_id namespace ownership).
offset size type field
────────────────────────────────────────────────────────────
0 8 u64 inst_id instrument ID; 0 for non-instrument-scoped messages (STATUS, QUERY_BALANCES, QUERY_ORDERS scope=all, QUERY_POSITIONS scope=all, CANCEL_ALL scope=all)
8 8 u64 exch_ts nanos since epoch; exchange event time (response ring); 0 on request ring
16 8 u64 rx_ts nanos since epoch; orderd receive time (response ring); 0 on request ring
24 8 u64 pub_ts nanos since epoch; ring publish time (both rings)
32 8 u64 seq monotone within ring (see Sequencing)
40 4 u32 epoch per-venue session counter (response ring); 0 on request ring
44 2 u16 schema_ver wire format version
46 1 u8 msg_type see per-ring enum above
47 1 u8 venue venue ID from metadata region
48 2 u16 flags response flags (response ring); reserved 0 (request ring)
50 2 u16 payload_len byte length of payload after this header
52 4 u32 _reserved
────────────────────────────────────────────────────────────
56 bytes
payload_len is the byte length of the message-specific body that follows the header. The total frame size (as seen in ring framing) is 56 + payload_len.
Request ring field usage¶
| Field | Usage |
|---|---|
| inst_id | Instrument for this request; 0 for venue-scoped queries (QUERY_BALANCES, QUERY_ORDERS scope=all, QUERY_POSITIONS scope=all) and CANCEL_ALL scope=all |
| exch_ts | 0 (not applicable) |
| rx_ts | 0 (not applicable) |
| pub_ts | Client's publish timestamp |
| seq | Client request sequence; monotone |
| epoch | 0 |
| msg_type | Request type enum (1–8) |
| venue | Target venue |
| flags | Reserved; always 0. Order-specific flags (POST_ONLY, REDUCE_ONLY) are in the NEW payload's flags field. |
Response ring field usage¶
| Field | Usage |
|---|---|
| inst_id | Instrument for this message; 0 for STATUS and venue-scoped query responses (BALANCES, ORDERS/POSITIONS when the request used inst_id = 0) |
| exch_ts | Exchange timestamp of the event; 0 if venue does not provide |
| rx_ts | When orderd received the exchange message |
| pub_ts | When orderd published to the response ring |
| seq | Response sequence; monotone across all msg_types and instruments |
| epoch | Per-venue session counter; increments on reconnect |
| msg_type | Response type enum (1–13) |
| venue | Source venue |
| flags | Response flags bitfield |
Epoch changes require reconciliation
When epoch changes for a venue, all non-terminal orders for that venue are uncertain. See Integration guide — Sequencing for the recovery procedure.
Request messages (client → orderd)¶
NEW¶
Submit a new order. order_id MUST be unique across the request stream. inst_id and venue are set in the header.
Payload: 32 bytes. Total frame: 88 bytes.
offset size type field
────────────────────────────────────────
0 8 u64 order_id client-assigned unique ID
8 8 i64 px price (ticks)
16 8 i64 qty quantity (steps)
24 1 u8 side BUY=1, SELL=2
25 1 u8 tif GTC=1, IOC=2, FOK=3
26 1 u8 flags order_flags bitfield (POST_ONLY, REDUCE_ONLY)
27 5 _reserved
orderd validates the request locally before sending to the exchange:
order_idmust not already exist →DUPLICATE_ORDERinst_idmust be ACTIVE in metadata →INVALID_INSTRUMENTpxmust be a valid multiple of the instrument's price tick →INVALID_PRICEqtymust be a valid multiple of the instrument's qty step →INVALID_QTYvenuemust be connected →DISCONNECTED- Flags must be supported by the target venue →
REDUCE_ONLY(if venue doesn't support and orderd cannot emulate)
On validation failure, orderd publishes an ORDER_REJECT with the LOCAL flag set in the response header. The request is never sent to the exchange.
CANCEL¶
Cancel a specific order by order_id.
Payload: 8 bytes. Total frame: 64 bytes.
offset size type field
────────────────────────────────────────
0 8 u64 order_id which order to cancel
orderd validates locally:
order_idmust exist and be in a non-terminal state →ORDER_NOT_FOUND- Order must not be in PENDING_MODIFY or PENDING_CXL →
MODIFY_IN_FLIGHT
Cancel during PENDING_NEW is allowed — orderd transitions the order to PENDING_CXL and sends the cancel to the exchange. See Integration guide — Mutations during PENDING_NEW.
MODIFY¶
Modify the price of an order. Allowed in LIVE or PENDING_NEW states. Quantity changes are not supported — use cancel + new.
Payload: 16 bytes. Total frame: 72 bytes.
offset size type field
────────────────────────────────────────
0 8 u64 order_id which order to modify
8 8 i64 new_px new price (ticks)
orderd validates locally:
order_idmust exist and be in LIVE or PENDING_NEW →ORDER_NOT_FOUND- Order must not be in PENDING_MODIFY or PENDING_CXL →
MODIFY_IN_FLIGHT new_pxmust be a valid tick multiple →INVALID_PRICE- Quantity change attempted →
QTY_MODIFY
orderd transitions the order to PENDING_MODIFY and sends the amend to the exchange. During PENDING_NEW, the exchange will process the amend after the preceding NEW on the same connection. See Integration guide — Mutations during PENDING_NEW.
CANCEL_ALL¶
Cancel all open orders. Scope determined by the header:
inst_id = 0→ cancel all orders on this venueinst_id = X→ cancel all orders for instrument X on this venue
CANCEL_ALL targets a single venue, specified by venue in the header. To cancel across all venues, send one CANCEL_ALL per venue.
Payload: 0 bytes. Total frame: 56 bytes.
orderd iterates all matching open orders, transitions each to PENDING_CXL, and sends individual cancels. The client receives:
- Individual
CANCEL_ACKfor each successfully cancelled order - Individual
CANCEL_REJECTfor any that fail (e.g., already filled) - A final
CANCEL_ALL_ACKsummary when all cancel responses have been received (or after the 5-second timeout)
Fills may interleave with the individual CANCEL_ACK stream — a fill arriving between individual cancel responses is normal.
QUERY_BALANCES¶
Request current balances for the venue specified in the header. inst_id is 0.
Payload: 0 bytes. Total frame: 56 bytes.
orderd queries the exchange and publishes a BALANCES response.
QUERY_ORDERS¶
Request open orders. Scope determined by the header:
inst_id = 0→ all open orders on this venueinst_id = X→ open orders for instrument X
Payload: 0 bytes. Total frame: 56 bytes.
orderd queries the exchange and publishes an ORDERS response.
QUERY_ORDER¶
Check the status of a specific order.
Payload: 8 bytes. Total frame: 64 bytes.
offset size type field
────────────────────────────────────────
0 8 u64 order_id which order to check
orderd publishes an ORDER_STATUS response with the order's current state.
QUERY_POSITIONS¶
Request open positions for the venue specified in the header. inst_id is 0 for all positions, or a specific instrument ID for a single position.
Payload: 0 bytes. Total frame: 56 bytes.
orderd queries the exchange and publishes a POSITIONS response. Only applicable to derivatives venues (perp/futures). On spot-only venues, orderd publishes an empty POSITIONS response.
Query correlation and completion contract¶
Request headers do not carry a query correlation ID. Clients MUST use the following outstanding-request discipline.
Outstanding limits:
QUERY_BALANCES: at most one outstanding request pervenue.QUERY_ORDERS: at most one outstanding request per(venue, inst_id scope).QUERY_POSITIONS: at most one outstanding request per(venue, inst_id scope).QUERY_ORDER: may be outstanding concurrently for differentorder_idvalues.
If a client sends overlapping requests outside this contract, response correlation is undefined.
Completion semantics:
QUERY_BALANCES: oneBALANCESframe completes the request.QUERY_ORDERS: oneORDERSframe completes the request.QUERY_POSITIONS: onePOSITIONSframe completes the request (spot-only venues returnn_positions = 0).QUERY_ORDER: oneORDER_STATUSframe with matchingorder_idcompletes the request.
Timeout semantics:
- If no completion frame arrives before client timeout, request outcome is unknown.
- Client MUST treat the outstanding slot as unresolved until it retries and receives completion.
Query timeout defaults (recommended)¶
Use these client timeout defaults unless venue-specific behavior requires overrides:
QUERY_ORDER:500msQUERY_BALANCES:1500msQUERY_ORDERS:1500msQUERY_POSITIONS:1500ms
During reconnect/reconciliation flows, use a wider timeout budget:
- all query types:
3000ms
Retry policy after timeout:
- retry with bounded backoff (
25msinitial,x2, cap200ms) - keep one outstanding request per constrained scope until completion
Response messages (orderd → client)¶
ORDER_ACK¶
New order accepted by the exchange. Order transitions to LIVE — unless a modify or cancel was sent during PENDING_NEW, in which case order_state reflects the in-flight mutation (PENDING_MODIFY or PENDING_CXL).
Payload: 48 bytes. Total frame: 104 bytes.
offset size type field
────────────────────────────────────────
0 8 u64 order_id client order ID (echo)
8 8 u64 exch_oid exchange order ID (xxHash64 if string)
16 8 i64 px confirmed price (ticks)
24 8 i64 qty confirmed qty (steps)
32 8 i64 filled_qty cumulative filled (steps); 0 for new order
40 1 u8 order_state LIVE (2), PENDING_MODIFY (3), or PENDING_CXL (4)
41 1 u8 side BUY/SELL (echo)
42 6 _pad
ORDER_REJECT¶
New order rejected. Order transitions to REJECTED (terminal).
Payload: 16 bytes. Total frame: 72 bytes.
offset size type field
────────────────────────────────────────
0 8 u64 order_id client order ID (echo)
8 1 u8 reject_reason see reject_reason enum
9 1 u8 order_state REJECTED (6)
10 6 _pad
When the LOCAL flag is set in the response header, the order was rejected by orderd's local validation and never sent to the exchange.
MODIFY_ACK¶
Price modification accepted. Order transitions from PENDING_MODIFY to LIVE with the new price confirmed.
Payload: 48 bytes. Total frame: 104 bytes.
offset size type field
────────────────────────────────────────
0 8 u64 order_id client order ID
8 8 u64 exch_oid exchange order ID (may change on some venues)
16 8 i64 px new confirmed price (ticks)
24 8 i64 qty order qty (steps) — unchanged
32 8 i64 filled_qty cumulative filled (steps)
40 1 u8 order_state LIVE (2)
41 7 _pad
On some venues, exch_oid may differ from the original after a modify. orderd handles the mapping internally — the client's order_id is unchanged.
MODIFY_REJECT¶
Price modification rejected. Order transitions from PENDING_MODIFY to LIVE with original price unchanged.
Payload: 32 bytes. Total frame: 88 bytes.
offset size type field
────────────────────────────────────────
0 8 u64 order_id client order ID
8 8 i64 px confirmed price (unchanged original)
16 8 i64 filled_qty cumulative filled (steps)
24 1 u8 order_state LIVE (2)
25 1 u8 reject_reason see reject_reason enum
26 6 _pad
CANCEL_ACK¶
Cancel confirmed. Order transitions to CANCELED (terminal).
Payload: 24 bytes. Total frame: 80 bytes.
offset size type field
────────────────────────────────────────
0 8 u64 order_id client order ID
8 8 i64 filled_qty cumulative filled at cancellation (steps)
16 1 u8 order_state CANCELED (7)
17 7 _pad
CANCEL_REJECT¶
Cancel rejected. Order transitions from PENDING_CXL to LIVE — the cancel failed and the order is still resting.
Payload: 24 bytes. Total frame: 80 bytes.
offset size type field
────────────────────────────────────────
0 8 u64 order_id client order ID
8 8 i64 filled_qty cumulative filled (steps)
16 1 u8 order_state LIVE (2)
17 1 u8 reject_reason see reject_reason enum
18 6 _pad
Not emitted when the NEW itself is rejected during PENDING_NEW. That path emits ORDER_REJECT.
CANCEL_ALL_ACK¶
Completion signal for a CANCEL_ALL request. Published after all individual CANCEL_ACK / CANCEL_REJECT messages have been sent. orderd applies a timeout (default: 5 seconds) — if individual cancels have not all resolved, CANCEL_ALL_ACK is published with the current counts and timed-out orders in failed_count.
Payload: 12 bytes. Total frame: 68 bytes.
offset size type field
────────────────────────────────────────
0 4 u32 total_count total orders targeted
4 4 u32 cancelled_count orders successfully cancelled
8 4 u32 failed_count orders that failed (filled, rejected, timed out)
total_count = cancelled_count + failed_count.
FILL¶
A fill (execution) event. Updates the order's filled_qty. If filled_qty == order_qty, order transitions to FILLED (terminal). order_state is the post-fill state.
Payload: 80 bytes. Total frame: 136 bytes.
offset size type field
────────────────────────────────────────
0 8 u64 order_id client order ID
8 8 u64 exch_oid exchange order ID
16 8 u64 fill_id exchange fill ID (xxHash64 if string)
24 8 i64 fill_px fill price (ticks) — the execution price
32 8 i64 fill_qty this fill quantity (steps)
40 8 i64 filled_qty cumulative filled after this fill (steps)
48 8 i64 order_qty total order qty (steps)
56 8 i64 fee fee amount (fee asset decimal units; negative = rebate)
64 8 u64 fee_asset_id fee currency asset_id
72 1 u8 order_state PENDING_NEW (1), LIVE (2), PENDING_MODIFY (3), PENDING_CXL (4), or FILLED (5)
73 1 u8 side BUY/SELL
74 1 u8 is_maker 1 = maker, 0 = taker
75 5 _pad
Fee encoding. fee is encoded as an integer in the fee asset's smallest decimal unit, using the decimals field from the asset metadata. Example: if the fee is 0.001 BTC and BTC has decimals = 8, then fee = 100000. Negative values indicate a rebate (e.g., maker rebate on some venues).
fee_asset_id MUST reference a valid asset in the metadata region — orderd ensures the fee asset is registered before publishing the fill. Fees are best-effort at fill time; final fee reconciliation (e.g., VIP rebates applied at settlement) may require out-of-band data.
Fills are the source of truth for fees
The instrument struct fee fields (make_fee_bps, take_fee_bps) in metadata are best-effort estimates. For any strategy where fee accuracy matters, read fees from fill messages and maintain a local cache. The is_maker field tells you which side of the fee schedule applies.
Deduplication. orderd guarantees no duplicate fill_id values on the response ring within a given epoch. Clients do not need to implement fill dedup. The fill_id field remains useful for correlating with exchange records and trade history.
order_id = 0 is reserved for reconciliation orphans (exchange-side orders unknown to the client/gateway mapping). Treat these fills as reconciliation records, not normal strategy callbacks.
BALANCES¶
Response to QUERY_BALANCES. Contains all balances for the queried venue.
Payload: variable. Minimum 4 bytes.
offset size type field
────────────────────────────────────────
0 2 u16 n_balances
2 2 u16 _pad
4 ... BalanceEntry[n_balances]
Payload size: 4 + n_balances × 24 bytes.
BalanceEntry (24 bytes)¶
offset size type field
────────────────────────────────────────
0 8 u64 asset_id asset identifier
8 8 i64 available free balance (asset decimal units)
16 8 i64 locked in-use balance (asset decimal units)
Total balance = available + locked. Both values use the asset's decimals field from metadata for conversion to real values.
ORDERS¶
Response to QUERY_ORDERS. Contains all open orders matching the query scope.
Payload: variable. Minimum 4 bytes.
offset size type field
────────────────────────────────────────
0 2 u16 n_orders
2 2 u16 _pad
4 ... OrderEntry[n_orders]
Payload size: 4 + n_orders × 64 bytes.
OrderEntry (64 bytes)¶
offset size type field
────────────────────────────────────────
0 8 u64 order_id client order ID
8 8 u64 exch_oid exchange order ID
16 8 u64 inst_id instrument ID
24 8 i64 px current confirmed price (ticks)
32 8 i64 qty order qty (steps)
40 8 i64 filled_qty cumulative filled (steps)
48 1 u8 order_state current state
49 1 u8 side BUY/SELL
50 1 u8 tif GTC/IOC/FOK
51 1 u8 flags order_flags
52 4 _pad
56 8 u64 create_ts order creation timestamp (nanos since epoch)
Each OrderEntry includes inst_id because the response may contain orders across multiple instruments (when QUERY_ORDERS uses inst_id = 0 in the header).
ORDER_STATUS¶
Response to QUERY_ORDER. Single order's full state. Same layout as OrderEntry.
Payload: 64 bytes. Total frame: 120 bytes.
order_id = 0 indicates an order discovered during reconciliation that has no client-owned request identity. Route to reconciliation handling only.
POSITIONS¶
Response to QUERY_POSITIONS. Contains open positions for the queried venue.
Payload: variable. Minimum 4 bytes.
offset size type field
────────────────────────────────────────
0 2 u16 n_positions
2 2 u16 _pad
4 ... PositionEntry[n_positions]
Payload size: 4 + n_positions × 56 bytes.
PositionEntry (56 bytes)¶
offset size type field
────────────────────────────────────────
0 8 u64 inst_id instrument ID
8 8 i64 qty signed position size (steps); positive=long, negative=short
16 8 i64 entry_px average entry price (ticks)
24 8 i64 unrealized_pnl unrealized PnL (quote asset decimal units)
32 8 i64 margin allocated margin (quote asset decimal units)
40 8 i64 liquidation_px estimated liquidation price (ticks); 0 if unavailable
48 4 u32 leverage leverage multiplier (e.g., 10 = 10x)
52 4 _pad
Position sign convention: qty > 0 = long, qty < 0 = short. Convert to real values using the instrument's step size from metadata.
STATUS¶
Connection health and heartbeat for orderd. Emitted periodically (every 1 second). inst_id is 0 in the header — this message is per-venue, not per-instrument.
Payload: 32 bytes. Total frame: 88 bytes.
offset size type field
────────────────────────────────────────
0 8 u64 last_rx_age_ns nanos since last exchange message
8 4 u32 reconnect_count
12 4 u32 open_orders number of tracked open orders
16 4 u32 pending_requests requests currently in flight to exchange
20 1 u8 conn_state 1=CONNECTED, 2=DISCONNECTED, 3=RECONNECTING
21 3 _pad
24 8 u64 _reserved
If last_rx_age_ns exceeds 5 seconds, the consumer MUST treat the connection as stale and stop submitting new requests on that venue until recovery.
Different from market data STATUS
The order routing STATUS payload tracks open orders and pending requests instead of market data's gap/drop counters and active instruments. Both share the same conn_state enum values.
Venue support matrix¶
Not every venue supports every feature.
| Feature | Binance | Bybit | Coinbase | Hyperliquid |
|---|---|---|---|---|
| NEW | yes | yes | yes | yes |
| CANCEL | yes | yes | yes | yes |
| MODIFY (price) | yes | yes | yes | yes |
| CANCEL_ALL | yes | yes | individual | yes |
| POST_ONLY | yes | yes | yes | yes |
| REDUCE_ONLY | yes | yes | — | yes |
| IOC | yes | yes | yes | yes |
| FOK | yes | yes | yes | yes |
| QUERY_BALANCES | yes | yes | yes | yes |
| QUERY_ORDERS | yes | yes | yes | yes |
| QUERY_POSITIONS | yes | yes | empty | yes |
Venue fallback and reject contract¶
| Scenario | Behavior |
|---|---|
Coinbase CANCEL_ALL |
No native endpoint. orderd fans out individual cancels; emits per-order CANCEL_ACK/CANCEL_REJECT, then CANCEL_ALL_ACK. |
Coinbase REDUCE_ONLY on NEW |
Local reject: ORDER_REJECT with reject_reason = REDUCE_ONLY and LOCAL flag set. |
Coinbase QUERY_POSITIONS |
Supported as empty response: POSITIONS with n_positions = 0; no reject frame. |
Any venue CANCEL_ALL timeout path |
CANCEL_ALL_ACK.failed_count includes unresolved or timed-out cancels. |