FIX
Who this document is for
This is the complete reference for connecting to the account's FIX gateway. It is written for two audiences, and both get everything they need from this one document:
- Trading clients / algos — order entry, market data, reference data, position and trade queries.
- Clearing firms / risk desks / drop-copy consumers — real-time, read-only visibility into all account activity (see section 11, Drop Copy), without needing to place a single order.
Every message type, every field, and every enumerated value the gateway sends or accepts is documented below — nothing is left implicit. Section 14 is an explicit, exhaustive list of what is not supported, so there should be no ambiguity about gateway behavior after reading this document. If something you need isn't covered here, it isn't implemented — contact your account representative rather than assuming.
Table of Contents
- Overview & Connectivity
- Session Layer
- Authentication & Authorization
- Order Entry
- Execution Reports & Cancel Rejects
- Market Data
- Reference Data
- Positions
- Trade Capture
- Error Handling
- Drop Copy — Guide for Clearing Firms and Drop-Copy Consumers
- Common Message Sequences
- Reconnection & Reconciliation
- Known Limitations
- Appendix A — Tag Index
- Appendix B — Enumerations Reference
1. Overview & Connectivity
- Protocol: FIX 4.x over raw TCP. One TCP connection = one FIX session; no multiplexing of multiple logical sessions over one socket.
- FIX version: not fixed server-side. On
Logon, the server reads the client's ownBeginString(8) and echoes that exact value back on every reply for the rest of the session. This gateway is built and tested against FIX.4.4 — sendBeginString=FIX.4.4unless your account representative tells you otherwise. The client's declared version is not validated against a supported list, so a mismatched version will not be rejected outright, but field behavior described in this document assumes FIX.4.4 semantics. - Message framing: standard FIX tag=value, SOH(
\x01)-delimited, withBodyLength(9) and a trailingCheckSum(10) computed normally. - Connection details (host, port, credentials, and your
SenderCompID/TargetCompIDconvention) are provided per account by your account representative — they are not fixed values in this document. - Header fields on every server message:
BeginString(8) (echoed, see above),BodyLength(9),MsgType(35),SenderCompID(49)/TargetCompID(56) (swapped from what you sent — see below),MsgSeqNum(34) (incrementing per session, starting at 1),SendingTime(52),CheckSum(10). - CompID swap: the server's
SenderCompID(49) on every reply is yourTargetCompID(56) fromLogon; the server'sTargetCompID(56) is yourSenderCompID(49) fromLogon. This is standard FIX convention. - Sequence numbers: incoming
MsgSeqNumgaps are logged and the server resynchronizes to whatever it next receives — they are not enforced or rejected. Do not rely on the gateway to detect or refuse a sequence gap on your side; see section 13 for how to reconcile state safely regardless.
2. Session Layer
Logon (35=A) — client → server
| Tag | Name | Req'd | Values/Format | Notes |
|---|---|---|---|---|
| 98 | EncryptMethod | Y | 0 only |
Any value sent is ignored; the server always replies with 0. |
| 108 | HeartBtInt | Y | seconds, int | Drives the server's heartbeat cadence for this session (see Session liveness below). |
| 553 | Username | N | string | If absent, SenderCompID(49) is used as the username instead — see Auth fallback below. |
| 554 | Password | Y | string | Always required, regardless of which identifier path is used. |
| 49 | SenderCompID | Y | string | Becomes the login identifier if tag 553 is absent. |
| 56 | TargetCompID | Y | string |
Auth fallback: if Username(553) is not sent, the server uses SenderCompID(49) as the username. This exists because many FIX engines don't expose tag 553 directly — set your account's username into SenderCompID and it works without needing 553 at all.
Reject: FIX has no dedicated logon-reject message type. On bad credentials, unknown user, or an account that is already logged in on this transport, the server replies with Logout(35=5) carrying Text(58) with a human-readable reason, then closes the TCP connection. There is no machine-readable reject code on this path — parse Text(58), or check with your account representative if a login is failing unexpectedly.
Success reply — server → client, Logon(35=A): EncryptMethod(98)=0, HeartBtInt(108) echoed back.
One session per account per transport: an account can have exactly one active FIX session at a time. A second Logon attempt with the same credentials while a session is already active is rejected (see above) until the first session logs out or times out.
Heartbeat (35=0)
Bidirectional. You may send one when idle, or in reply to a TestRequest; the server does the same (see Session liveness). TestReqID(112) is echoed back only when a Heartbeat is sent in reply to a TestRequest.
TestRequest (35=1) — client → server
TestReqID(112) required. The server always replies with Heartbeat(35=0) echoing the same TestReqID.
ResendRequest (35=2) — client → server
BeginSeqNo(7) is accepted but not used to select content. The server always replies with SequenceReset(35=4), GapFillFlag(123)=Y, NewSeqNo(36)=its next outbound sequence number. No message replay is performed — this is gap-fill only, never actual history. See section 13 for how to reconcile state that was genuinely missed.
SequenceReset (35=4) — client → server
NewSeqNo(36) sets the server's expected next inbound sequence number. No reply is sent.
Logout (35=5)
Either direction. The server replies with its own Logout(35=5), then closes the TCP connection. The account's login slot for this transport is released so a new Logon can succeed — this release is not necessarily instantaneous with the Logout reply, so a reconnect attempt immediately after logging out should retry briefly on a rejected Logon rather than treat one failure as final.
Session liveness (server-driven)
- The server sends an unsolicited
Heartbeat(0) afterHeartBtIntseconds of send-idle (nothing sent to you). - The server sends
TestRequest(1) after1.5 × HeartBtIntseconds of receive-idle (nothing received from you). - The server disconnects after
2 × HeartBtIntseconds of receive-idle with no reply to thatTestRequest.
Set HeartBtInt to a value your engine can reliably keep up with — there is no minimum or maximum enforced, but a very small value will cause spurious disconnects under any network jitter.
3. Authentication & Authorization
Credentials are matched against server-side account records by Username/SenderCompID + Password. Each account has a role, and some requests are restricted by role — a request from a role that isn't permitted for that action is explicitly rejected with the appropriate reject message for that request type, never silently dropped.
| Action | TRADER | VIEWER | CLEARER / ADMIN |
|---|---|---|---|
Market data subscribe (MarketDataRequest) |
✅ | ❌ (MarketDataRequestReject) |
❌ |
| Submit / cancel / replace / mass-cancel orders | ✅ | ❌ (explicit reject on the specific request) | ✅ |
| Order / position / trade status queries, reference data | ✅ | ✅ | ✅ |
Real-time ExecutionReport copies for all account activity |
✅ | ✅ | ✅ |
VIEWER is the natural role for a clearing firm or drop-copy consumer: it has full read access to everything happening on the account (orders, fills, positions, trade history, in real time) and is structurally incapable of submitting, canceling, or replacing an order, or subscribing to market data. See section 11.
Every logged-in session, regardless of role, receives the same real-time copy of account activity — this is not a VIEWER-only behavior, any role can be used as a drop-copy feed if desired, though VIEWER is recommended for a pure risk/clearing connection specifically because it cannot also act on the account.
4. Order Entry
NewOrderSingle (35=D) — client → server
| Tag | Name | Req'd | Values/Format | Notes |
|---|---|---|---|---|
| 11 | ClOrdID | Y | string | Your unique identifier for this order. |
| 55 | Symbol | Y | string | Must match a known instrument — otherwise rejected via BusinessMessageReject(j), not ExecutionReport (see section 10). |
| 54 | Side | Y | 1=Buy, 2=Sell |
|
| 40 | OrdType | Y | see table below | |
| 38 | OrderQty | Y | int, >0 | |
| 44 | Price | conditional | decimal | Required for OrdType 2 (Limit) and Limit+IOC. |
| 99 | StopPx | conditional | decimal | Required for OrdType 3 (Stop) and 4 (Stop Limit). |
| 59 | TimeInForce | N | 0 (Day, default) or 3 (IOC) |
Any other value (1=GTC, 2=OPG, 4=FOK, 5=GTX, 6=GTD) is rejected outright, not silently downgraded to Day. |
| 1138 | DisplayQty | N | int, >0 | Iceberg display quantity. Omit for a fully-displayed order. |
| 110 | MinQty | N | int, >0 | Minimum fill quantity. |
| 21 | HandlInst | N | not enforced | Accepted but not validated. |
| 60 | TransactTime | N | not read | Accepted but not used — the server stamps its own time. |
OrdType(40) accepted combinations:
| OrdType | TimeInForce | Meaning | Requires |
|---|---|---|---|
1 |
0 |
Market | — |
2 |
0 |
Limit | Price(44) |
3 |
0 |
Stop | StopPx(99) |
4 |
0 |
Stop Limit | Price(44) + StopPx(99) |
2 |
3 |
Limit, Immediate-or-Cancel — maps to the account's native "FAK" order type: partial fills allowed, unfilled remainder killed immediately, never rests on the book | Price(44) |
Any other OrdType/TimeInForce combination — including IOC combined with Stop or Stop Limit — is rejected. There is no true all-or-none Fill-or-Kill, GTC, GTD, OPG, or GTX support; see section 14.
Reply: ExecutionReport(8), ExecType/OrdStatus=8 (Rejected), for any order-level reject the gateway's own field/role validation catches before submission (VIEWER role, Side not set, unsupported TimeInForce, unsupported OrdType/TimeInForce combination, OrderQty not > 0), or a reject from the account's own pre-trade risk/validation checks (price outside the instrument's price band, off-tick price, size/risk limits). The latter carries OrdRejReason(103) — see Appendix B for the full value table and which side (gateway pre-trade check vs. the exchange itself, asynchronously) can produce which values. An unknown Symbol is handled differently: it gets BusinessMessageReject(j) instead of an ExecutionReport, since the instrument itself, not the order's fields, is what's invalid.
OrderCancelRequest (35=F) — client → server
| Tag | Name | Req'd | Notes |
|---|---|---|---|
| 11 | ClOrdID | Y | This cancel's own ID — becomes the "current" ClOrdID for the order afterward, for any later cancel/replace using OrigClOrdID. |
| 41 | OrigClOrdID | conditional | Required if OrderID(37) is not sent. |
| 37 | OrderID | conditional | Primary lookup key. If present, used directly; if absent, the order is looked up by matching OrigClOrdID against the last-known ClOrdID for each of this session's open orders. |
Scope: session-scoped — you can only cancel an order this same session originally submitted (or amended). See section 13 for what this means across a reconnect.
Reply: ExecutionReport(8) on success, OrderCancelReject(9) on failure (unknown order, VIEWER role, or a genuine backend rejection of the cancel attempt) — see section 5.
OrderCancelReplaceRequest (35=G) — client → server
Same dual lookup as OrderCancelRequest (OrderID primary, OrigClOrdID fallback), same session scope. Only Price(44) and OrderQty(38) can be amended — TimeInForce and StopPx are not applied even if sent. Omit a field to leave that part of the order unchanged.
Reply: ExecutionReport(8) on success, OrderCancelReject(9) on failure.
OrderStatusRequest (35=H) — client → server
Dual lookup: OrderID(37) primary, ClOrdID(11) fallback if OrderID is absent or not found.
Scope: account-wide — works for any order on the account, regardless of which session originally submitted it, including orders from a prior, now-disconnected session. This is the key reconciliation tool after a reconnect — see section 13.
Reply: ExecutionReport(8) reflecting the order's current state, or BusinessMessageReject(j) if the order can't be found.
OrderMassStatusRequest (35=AF) — client → server
| Tag | Name | Req'd | Notes |
|---|---|---|---|
| 55 | Symbol | N | Optional filter. |
| 54 | Side | N | Optional filter (1/2). |
Scope: account-wide — reports every open order on the account (not just this session's own), narrowed by Symbol/Side if given. There is no CME-style MassStatusReqType scoping beyond that (the tag is not read).
Reply: one ExecutionReport(8) per matching order. No matching orders → no reply at all (unlike TradeCaptureReportRequest/RequestForPositions, which always send at least one reply — this asymmetry is a known limitation, see section 14).
OrderMassCancelRequest (35=q) — client → server
Same Symbol/Side filters as OrderMassStatusRequest, but session-scoped, unlike the status queries above — only cancels orders this session itself submitted or amended (the same safety boundary as OrderCancelRequest/OrderCancelReplaceRequest). There is no CME-style MassCancelRequestType scoping beyond Symbol/Side (the tag is not read). VIEWER role → BusinessMessageReject(j), BusinessRejectReason=6 (Not authorized).
Reply: one ExecutionReport(8) per matching order, reflecting whatever actually happened to it — OrdStatus=4 (Canceled) if it canceled, its prior unchanged OrdStatus if that specific order's cancel attempt failed. This lets you tell a genuine per-order failure apart from silence, rather than guessing from an incomplete batch. No matching orders → no reply.
5. Execution Reports & Cancel Rejects
ExecutionReport (35=8) — server → client
| Tag | Name | Always sent? | Notes |
|---|---|---|---|
| 1 | Account | Y | |
| 37 | OrderID | Y | "NONE" if the order was rejected pre-submission (never reached the account). |
| 11 | ClOrdID | usually | Omitted only if no ClOrdID is on record for the order. |
| 17 | ExecID | Y | |
| 150 | ExecType | Y | See code table below. |
| 39 | OrdStatus | Y | See code table below. |
| 103 | OrdRejReason | on reject | Present only when ExecType=8. See Appendix B for the value table and source (gateway pre-trade check vs. exchange, asynchronously). |
| 55 | Symbol | Y | |
| 54 | Side | Y | 1/2 |
| 40 | OrdType | Y | |
| 59 | TimeInForce | Y | 0 or 3. |
| 38 | OrderQty | Y | |
| 1138 | DisplayQty | conditional | Present if the order was submitted with one. |
| 110 | MinQty | conditional | Present if the order was submitted with one. |
| 44 | Price | conditional | Present for Limit, Stop Limit, and Limit+IOC. |
| 99 | StopPx | conditional | Present for Stop and Stop Limit. |
| 151 | LeavesQty | Y | |
| 14 | CumQty | Y | |
| 6 | AvgPx | Y | 0 until the first fill. |
| 32 | LastQty | on fill | Present only when this specific report represents a fill. |
| 31 | LastPx | on fill | |
| 60 | TransactTime | Y |
ExecType(150) / OrdStatus(39) code pairs actually used (both fields always carry the same code, except the Rejected-cancel/replace case noted):
| Code | Meaning | When |
|---|---|---|
A |
Pending New | Order accepted for pre-trade risk check, not yet acknowledged by the exchange. |
0 |
New | Acknowledged by the exchange. |
6 |
Pending Cancel | Cancel submitted, not yet confirmed. |
4 |
Canceled | |
E |
Pending Replace | Replace submitted, not yet confirmed. |
5 |
Replaced | |
8 |
Rejected | A new order was rejected (OrdStatus is also 8); or a cancel/replace attempt against a live order was rejected asynchronously by the exchange itself (ExecType=8 but OrdStatus stays 0 — the order is still live and unaffected). See the important note below. |
2 |
Filled | |
1 |
Partially Filled |
Important — two different shapes for a rejected cancel/replace: a cancel or replace request can be rejected in two structurally different ways, and they arrive as two different message types:
- Synchronous — the gateway itself (or the account's own immediate validation) rejects your
OrderCancelRequest/OrderCancelReplaceRequestright away → you getOrderCancelReject(9),CxlRejReason(102) — see below. - Asynchronous — your cancel/replace request was accepted and forwarded, but the exchange itself, some time later, rejects it (e.g. the order had already been filled) → this arrives as an unsolicited
ExecutionReport(8),ExecType=8/OrdStatus=0(still New/live), withOrdRejReason(103) carrying the exchange's own reason — not anOrderCancelReject. If you are building reconciliation logic that only watches forOrderCancelReject(9) to detect a failed cancel, you will miss this case — watchExecType=8onExecutionReportas well.
OrderCancelReject (35=9) — server → client
| Tag | Name | Notes |
|---|---|---|
| 1 | Account | |
| 37 | OrderID | "NONE" if unknown. |
| 11 | ClOrdID | |
| 41 | OrigClOrdID | |
| 39 | OrdStatus | Always 8. |
| 434 | CxlRejResponseTo | 1=in response to OrderCancelRequest, 2=in response to OrderCancelReplaceRequest. |
| 102 | CxlRejReason | See Appendix B for the full value table. |
| 58 | Text | Human-readable reason — always present, read this alongside the numeric code. |
This message is only ever sent synchronously, as the direct reply to your own OrderCancelRequest/OrderCancelReplaceRequest on the same session. It is sent directly to the requesting session in every case. A genuine backend rejection of a cancel/replace against a real, previously-live order is also copied to every other logged-in session on the account (the same account-wide broadcast as ExecutionReport — see section 11), since it's real risk information a drop-copy observer needs. A VIEWER-role reject or an "unknown order" reject (a session-scoped lookup miss, not a real trading event) is sent only to the requesting session, not broadcast.
6. Market Data
MarketDataRequest (35=V) — client → server
| Tag | Name | Req'd | Notes |
|---|---|---|---|
| 262 | MDReqID | Y | Echoed on every reply for this subscription. |
| 263 | SubscriptionRequestType | Y | 1=Subscribe. 2=Unsubscribe. 0=Snapshot-only is treated as Subscribe (there is no one-shot-only mode). |
| 264 | MarketDepth | N | 1=top-of-book only. Any other value (including absent, or 0) also enables full order-book depth updates. |
| 146 | NoRelatedSym | N | Multi-symbol is supported — every Symbol(55) occurrence in the message is honored regardless of what this count field says. |
| 55 | Symbol | Y | One or more, for a watchlist-style subscription in a single message. |
| 267 | NoMDEntryTypes | N | |
| 269 | MDEntryType | N | 0=Bid, 1=Offer. Every occurrence is read; if none is given, both sides are sent. |
TRADER role only — VIEWER/CLEARER/ADMIN get MarketDataRequestReject. Unsubscribe (SubscriptionRequestType=2) gets no reply. An unknown symbol also gets MarketDataRequestReject.
MarketDataRequestReject(35=Y) carries MDReqID(262) and Text(58) only — there is no numeric MDReqRejReason code; parse Text for the cause ("Not authorized...", "Unknown symbol", "No Symbol specified").
MarketDataSnapshotFullRefresh (35=W) — server → client
Sent once per symbol, immediately on a successful subscribe. MDReqID(262), Symbol(55), NoMDEntries(268), then per entry: MDEntryType(269), MDEntryPx(270), MDEntrySize(271).
MarketDataIncrementalRefresh (35=X) — server → client
Sent on every book change for a subscribed symbol. Two independent update streams run per subscription:
- Top-of-book: always active regardless of
MarketDepth. One message per change, with an entry for whichever side(s) actually changed. - Full depth: active unless
MarketDepth=1was sent on subscribe. One message per price-level change, and each entry additionally carriesMDPriceLevel(1023) (1-based).
Each entry carries: MDUpdateAction(279), MDEntryType(269), Symbol(55), and — except on a Delete — MDEntryPx(270)/MDEntrySize(271). MDUpdateAction is computed per entry from whether that price level/side was previously empty: 0=New (was empty, now has size), 1=Change (had size both before and after), 2=Delete (now empty; price/size are omitted on this action).
7. Reference Data
SecurityListRequest (35=x) → SecurityList (35=y)
| Tag | Name | Notes |
|---|---|---|
| 320 | SecurityReqID | Echoed back. |
| 559 | SecurityListRequestType | 0 combined with Symbol(55) → single-instrument filter. Any other value (or absent) → full tradeable-instrument universe dump. |
Response: SecurityReqID(320), NoRelatedSym(146), then per instrument: Symbol(55), SecurityExchange(207).
SecurityDefinitionRequest (35=c) → SecurityDefinition (35=d)
Request: SecurityReqID(320), Symbol(55). Single-instrument lookup only.
Response fields: SecurityReqID(320), SecurityResponseID(322) (server-generated), TotNoRelatedSym(393) (1=found, 0=not found), Symbol(55), SecurityExchange(207), SecurityType(167) — FUT, OPT, or MLEG — MinPriceIncrement(969), ContractMultiplier(231), and LowLimitPrice(1148)/HighLimitPrice(1149) (the trading day's allowable price band, omitted if the instrument has none configured). Not found → Text="Unknown symbol", no instrument fields sent.
LowLimitPrice/HighLimitPrice are exactly the band NewOrderSingle's own pre-trade risk check validates against — a price outside this range is rejected with OrdRejReason=16 (see Appendix B).
8. Positions
RequestForPositions (35=AN) → PositionReport (35=AP)
Request: PosReqID(710), optional — echoed back on every reply. SubscriptionRequestType(263) selects the mode:
| Value | Meaning |
|---|---|
0 or absent |
Snapshot — one-time reply only. |
1 |
Snapshot + Updates — replies now with the current snapshot, then pushes a fresh PositionReport for a symbol every time a fill changes that symbol's position, until unsubscribed or the session ends. |
2 |
Unsubscribe — no reply, same convention as MarketDataRequest's unsubscribe. |
Response fields:
| Tag | Name | Notes |
|---|---|---|
| 710 | PosReqID | Echoed if you sent one. On a subscription push, this is the PosReqID from the original subscribe request. |
| 721 | PosMaintRptID | Server-generated, unique per report. |
| 728 | PosReqResult | 0=Valid, 2=No positions found. |
| 727 | TotalNumPosReports | Same value on every report in a batch. 1 on a subscription push (a single symbol, not a re-snapshot of everything). |
| 325 | UnsolicitedIndicator | N for a direct reply to your request, Y for a subscription push triggered by a fill. |
| 55 | Symbol | |
| 715 | ClearingBusinessDate | YYYYMMDD, current UTC date. |
| 702 | NoPositions | 1 |
| 703 | PosType | "TOT" (Total Transaction Qty). |
| 704 | LongQty | Gross filled long quantity for this symbol — not netted against ShortQty. |
| 705 | ShortQty | Gross filled short quantity for this symbol — not netted against LongQty. A symbol can carry both simultaneously (e.g. 2 long fills + 2 short fills reports LongQty=2, ShortQty=2, not a netted 0/0). |
| 753 | NoPosAmt | 1 |
| 707 | PosAmtType | "FMTM" (Final Mark-to-Market). |
| 708 | PosAmt | Open/unrealized P&L for this symbol. |
One PositionReport per symbol with activity. Zero positions on the account → a single report with PosReqResult=2, no position/amount fields. Margin and risk-limit figures are deliberately not sent over FIX — see section 14.
Push behavior is strictly opt-in via SubscriptionRequestType=1 — the server never sends an unsolicited PositionReport unless a session has explicitly subscribed. Send SubscriptionRequestType=2 to stop.
9. Trade Capture
TradeCaptureReportRequest (35=AD) → TradeCaptureReport (35=AE)
| Tag | Name | Req'd | Notes |
|---|---|---|---|
| 568 | TradeRequestID | N | Echoed back. |
| 55 | Symbol | N | Optional filter. |
| 37 | OrderID | N | Optional filter. |
Response, one per matching fill: TradeRequestID(568), TradeReportID(571, server-generated), TradeReportTransType(487)=0 (New), TotNumTradeReports(748) (same value on every report in the batch), LastRptRequested(912) (Y only on the final report in the batch), ExecID(17), OrderID(37), Symbol(55), Side(54), LastPx(31), LastQty(32), TransactTime(60).
Zero matches, or the trade log isn't available, → TradeCaptureReportRequestAck(35=AQ) instead, with TotNumTradeReports=0, LastRptRequested=Y (and Text="Trade log not available" specifically when the log itself couldn't be reached).
Scope: account-wide, not scoped to the requesting session — a brand-new connection that has never placed an order itself can send an unfiltered request and get the account's complete trade history. This is the primary tool for a clearing firm's trade reconciliation — see section 11.
10. Error Handling
- Reject (35=3) — session-level, sent for any application message received before
Logonhas completed successfully:RefSeqNum(45),SessionRejectReason(373)=99(Other — no more specific FIX 4.4 code fits "not logged on"),Text(58)="Not logged on". - BusinessMessageReject (35=j) — for a business-level problem not tied to a specific order's fields (unknown instrument, unknown ID, unsupported message, unauthorized request):
RefMsgType(372),RefSeqNum(45),BusinessRejectReason(380),Text(58).
| Cause | BusinessRejectReason(380) |
|---|---|
Recognized but unsupported MsgType (anything not listed in this document) |
3 = Unsupported Message Type |
Unknown Symbol on NewOrderSingle |
2 = Unknown Security |
OrderStatusRequest for an order that can't be found |
1 = Unknown ID |
Unauthorized OrderMassCancelRequest (VIEWER role) |
6 = Not authorized |
| Exchange-side error tied to an order, pushed unsolicited (not a reject of your own inbound request) | Passed through directly from the exchange's own reject reason, unclamped — the exchange defines codes well beyond FIX's own 0–7 range for this field (e.g. throttle limits, kill switch, risk controls), and those are preserved rather than folded into a generic 0 = Other. |
See Appendix B for the complete BusinessRejectReason enum, and section 5 for OrdRejReason(103)/CxlRejReason(102), which cover order- and cancel-specific rejects instead.
11. Drop Copy — Guide for Clearing Firms and Drop-Copy Consumers
This section is a self-contained guide for a firm that only needs read-only, real-time visibility into account activity — no order entry. It restates material from earlier sections in one place, specifically for this use case.
Setup
- Log on (
Logon, section 2) with a VIEWER-role account. VIEWER is recommended because it is structurally incapable of order entry or market data subscription — there's nothing to lock down on your end, the role does it. Any role technically receives the same real-time copy (see section 3), but VIEWER is the correct choice for a pure clearing/risk connection. - That's it — no subscription message is required for order/trade activity. It starts flowing immediately and automatically, for every order on the account, regardless of which other session submitted it.
- Optionally, also subscribe to live position updates:
RequestForPositions(AN),SubscriptionRequestType=1(section 8).
What you receive, unsolicited, in real time
| Message | When | Notes |
|---|---|---|
ExecutionReport(8) |
Every order-lifecycle event on the account: new-order acks, fills (partial and full), cancels, replaces, and rejects — both new-order rejects and asynchronous exchange-side cancel/replace rejects (ExecType=8, see the note in section 5). |
Not filtered by which session submitted the order — this is the entire account's activity. |
OrderCancelReject(9) |
Only for a genuine backend rejection of a cancel/replace against a real, previously-live order — see section 5 for exactly which case this is (as opposed to a VIEWER-role or unknown-order reject, which is not broadcast, since it isn't a real account event). | |
BusinessMessageReject(j) |
An exchange-side error tied to a specific order, unsolicited — see section 10. | |
PositionReport(AP) |
Only if you subscribed (SubscriptionRequestType=1) — pushed whenever a fill changes a symbol's position. |
Opt-in, not automatic. |
Reconciliation
For anything that might have been missed (a disconnect, a gap, a message that arrived before your system was ready to log it), do not rely on the live feed alone — poll the account-wide, request/reply endpoints instead, all of which work identically regardless of which session originally created the activity:
OrderStatusRequest(H) /OrderMassStatusRequest(AF) — current state of any order, or every open order.TradeCaptureReportRequest(AD) — complete fill history, optionally filtered by Symbol/OrderID.RequestForPositions(AN) — current position and open P&L per symbol.
See section 13 for the full reconciliation workflow after a disconnect. ResendRequest does not replay missed messages (see section 2) — the three account-wide endpoints above are the supported way to recover state, not sequence-number-based replay.
12. Common Message Sequences
(a) Session establishment
1. Client → Logon(A)
2. Server → Logon(A)
(b) Discover tradeable instruments
1. Client → SecurityListRequest(x)
2. Server → SecurityList(y)
3. (optional, per instrument) Client → SecurityDefinitionRequest(c) → Server → SecurityDefinition(d)
(c) Subscribe to market data
1. Client → MarketDataRequest(V), SubscriptionRequestType=1
2. Server → MarketDataSnapshotFullRefresh(W)
3. Server → MarketDataIncrementalRefresh(X) — repeated, on every book change
(d) Place and monitor a limit order through fill
1. Client → NewOrderSingle(D), OrdType=2
2. Server → ExecutionReport(8), ExecType=A (Pending New)
3. Server → ExecutionReport(8), ExecType=0 (New) — once acknowledged
4. Server → ExecutionReport(8), ExecType=2 or 1 (Filled / Partially Filled) — on fill(s)
(e) Amend then cancel a working order
1. Client → OrderCancelReplaceRequest(G), OrderID from step (d)
2. Server → ExecutionReport(8), ExecType=5 (Replaced)
3. Client → OrderCancelRequest(F), OrderID (or OrigClOrdID = the replace's own ClOrdID)
4. Server → ExecutionReport(8), ExecType=4 (Canceled)
(f) Query order status after a disconnect/reconnect
1. Client → Logon(A) on a new session
2. Client → OrderStatusRequest(H) or OrderMassStatusRequest(AF)
- Works the same as on the original session — these are account-wide (section 4), so orders submitted on a prior session are still visible. Cancel/replace of those orders is not — see section 13.
(g) Pull trade history (works the same on a brand-new session, e.g. right after Logon on first connect)
1. Client → TradeCaptureReportRequest(AD), optional Symbol/OrderID filter
2. Server → TradeCaptureReport(AE) × N, or TradeCaptureReportRequestAck(AQ) if none match
(h) Pull current positions
1. Client → RequestForPositions(AN)
2. Server → PositionReport(AP) × N (one per symbol with activity), or × 1 with PosReqResult=2 if none
(h2) Subscribe to position updates
1. Client → RequestForPositions(AN), SubscriptionRequestType=1
2. Server → PositionReport(AP) × N, same as a plain pull
3. Server → PositionReport(AP) again, whenever a fill changes a symbol's position, until the client sends RequestForPositions with SubscriptionRequestType=2, or the session ends
(i) Clean session teardown
1. Client → Logout(5)
2. Server → Logout(5), connection closed
(j) Drop copy — passive real-time monitoring of account activity
See section 11 for the complete guide. In short:
1. Client → Logon(A), VIEWER role recommended
2. Server → ExecutionReport(8) / OrderCancelReject(9) / BusinessMessageReject(j) pushed unsolicited as account activity happens
3. No further action needed — this is passive.
13. Reconnection & Reconciliation
A FIX session is not persistent across a disconnect — there is no message replay (ResendRequest is gap-fill only, see section 2), and order mutation requests (OrderCancelRequest/OrderCancelReplaceRequest/OrderMassCancelRequest) are scoped to the session that originally submitted or amended the order. This means, concretely, after a reconnect on a new session:
- Still works, unchanged, account-wide:
OrderStatusRequest,OrderMassStatusRequest,RequestForPositions,TradeCaptureReportRequest— every one of these sees the whole account's state and history, including everything from before the disconnect. - Does not carry over: the ability to
OrderCancelRequest/OrderCancelReplaceRequest/OrderMassCancelRequestan order that was submitted on the previous session, from the new session. If you need to cancel or amend an order across a reconnect, that specific limitation applies — contact your account representative if this is a blocking requirement for your workflow. - Not replayed: any
ExecutionReport/OrderCancelReject/BusinessMessageRejectpushed while you were disconnected is gone — it will not be resent. Reconcile using the account-wide endpoints above (they reflect current state, which is sufficient to detect anything you missed) rather than expecting the missed messages themselves to reappear.
Recommended reconnect sequence: Logon → OrderStatusRequest/OrderMassStatusRequest (confirm every order you believe is open still is, and its current LeavesQty/CumQty) → RequestForPositions (confirm positions match your own books) → TradeCaptureReportRequest if you need to backfill fill-level detail for the disconnected window.
14. Known Limitations
This is an exhaustive list — if a capability isn't mentioned here or elsewhere in this document as unsupported, and it isn't working as documented, that's a genuine issue to report, not an undocumented gap.
- TimeInForce: only Day(
0) and IOC(3) are supported. GTC, GTD, OPG, GTX, and true all-or-none FOK are rejected outright onNewOrderSingle— the account has no execution model for them (orders always live for a single session/day; IOC/FAK is the only "kill" semantic supported). - No party-identity fields:
PartyID/PartyRole/SenderSubID/TargetSubIDand similar CME-style identity/give-up fields are not read or sent. This gateway is a client-facing account gateway, not a direct exchange session — there is exactly one account per FIX session. - No margin/risk-limit figures over FIX: not available on
PositionReportor anywhere else in this protocol. This matches common DMA vendor practice (margin is typically a separate REST/portal concern, not a FIX concern) — it is not a planned addition. - Order mutation is session-scoped; queries are not:
OrderCancelRequest/OrderCancelReplaceRequest/OrderMassCancelRequestonly apply to orders the same session submitted or amended — no cross-reconnect visibility for mutation.OrderStatusRequest/OrderMassStatusRequest, positions, and trade history are all account-wide. See section 13. - No fine-grained mass request typing:
OrderMassStatusRequest/OrderMassCancelRequestsupport only Symbol/Side narrowing — there is no CME-styleMassStatusReqType/MassCancelRequestTypescoping beyond that (the tags are not read). OrderMassStatusRequestsends no reply when nothing matches — unlikeTradeCaptureReportRequest/RequestForPositions, which always reply with at least an empty/zero-result message. Treat "noExecutionReports arrived" as your zero-match signal for this one request type specifically.OrderCancelReplaceRequestcan only amend Price and OrderQty — not TimeInForce or StopPx, even if sent.ResendRequestnever replays real message history — it always answers with a gap-fill (SequenceReset,GapFillFlag=Y). See section 13 for how to recover state instead.- No numeric
MDReqRejReasononMarketDataRequestReject— only free-textText(58). DisplayQty/MinQtyare accepted and echoed, but not otherwise enforced by the gateway itself beyond passing them through to the account/exchange as submitted.- No multi-leg, quoting, or allocation workflows:
NewOrderMultileg,NewOrderCross,MassQuote,QuoteCancel, allocation instructions, and similar are not implemented. A recognized-but-unsupportedMsgTypegetsBusinessMessageReject(j),BusinessRejectReason=3.
Appendix A — Tag Index
Every tag this gateway reads or writes, numeric order. "Req'd" means required somewhere in this document — see the relevant section for the exact conditions.
| Tag | Name | Used in |
|---|---|---|
| 1 | Account | ExecutionReport |
| 6 | AvgPx | ExecutionReport |
| 7 | BeginSeqNo | ResendRequest |
| 8 | BeginString | header (all messages) |
| 9 | BodyLength | header (all messages) |
| 10 | CheckSum | trailer (all messages) |
| 11 | ClOrdID | NewOrderSingle, OrderCancelRequest, OrderCancelReplaceRequest, OrderStatusRequest, ExecutionReport, OrderCancelReject |
| 14 | CumQty | ExecutionReport |
| 15 | Currency | (accepted, not enforced) |
| 17 | ExecID | ExecutionReport, TradeCaptureReport |
| 21 | HandlInst | NewOrderSingle (accepted, not enforced) |
| 31 | LastPx | ExecutionReport, TradeCaptureReport |
| 32 | LastQty | ExecutionReport, TradeCaptureReport |
| 34 | MsgSeqNum | header (all messages) |
| 35 | MsgType | header (all messages) |
| 36 | NewSeqNo | SequenceReset |
| 37 | OrderID | most order/trade messages |
| 38 | OrderQty | NewOrderSingle, OrderCancelReplaceRequest, ExecutionReport |
| 39 | OrdStatus | ExecutionReport, OrderCancelReject |
| 40 | OrdType | NewOrderSingle, ExecutionReport |
| 41 | OrigClOrdID | OrderCancelRequest, OrderCancelReplaceRequest, OrderCancelReject |
| 44 | Price | NewOrderSingle, OrderCancelReplaceRequest, ExecutionReport |
| 45 | RefSeqNum | Reject, BusinessMessageReject |
| 49 | SenderCompID | header (all messages) |
| 52 | SendingTime | header (all messages) |
| 54 | Side | NewOrderSingle, OrderMassStatusRequest, OrderMassCancelRequest, ExecutionReport, TradeCaptureReport, MarketDataRequest filter |
| 55 | Symbol | most messages |
| 56 | TargetCompID | header (all messages) |
| 58 | Text | Logout, Reject, BusinessMessageReject, OrderCancelReject, MarketDataRequestReject, TradeCaptureReportRequestAck |
| 59 | TimeInForce | NewOrderSingle, ExecutionReport |
| 60 | TransactTime | NewOrderSingle (accepted, not read), ExecutionReport, TradeCaptureReport |
| 98 | EncryptMethod | Logon |
| 99 | StopPx | NewOrderSingle, ExecutionReport |
| 102 | CxlRejReason | OrderCancelReject |
| 103 | OrdRejReason | ExecutionReport |
| 108 | HeartBtInt | Logon |
| 110 | MinQty | NewOrderSingle, ExecutionReport |
| 112 | TestReqID | TestRequest, Heartbeat |
| 123 | GapFillFlag | SequenceReset |
| 146 | NoRelatedSym | MarketDataRequest, SecurityList |
| 150 | ExecType | ExecutionReport |
| 151 | LeavesQty | ExecutionReport |
| 167 | SecurityType | SecurityDefinition |
| 207 | SecurityExchange | SecurityList, SecurityDefinition |
| 231 | ContractMultiplier | SecurityDefinition |
| 262 | MDReqID | MarketDataRequest, MarketDataSnapshotFullRefresh, MarketDataIncrementalRefresh, MarketDataRequestReject |
| 263 | SubscriptionRequestType | MarketDataRequest, RequestForPositions |
| 264 | MarketDepth | MarketDataRequest |
| 267 | NoMDEntryTypes | MarketDataRequest |
| 268 | NoMDEntries | MarketDataSnapshotFullRefresh, MarketDataIncrementalRefresh |
| 269 | MDEntryType | MarketDataRequest, MarketDataSnapshotFullRefresh, MarketDataIncrementalRefresh |
| 270 | MDEntryPx | MarketDataSnapshotFullRefresh, MarketDataIncrementalRefresh |
| 271 | MDEntrySize | MarketDataSnapshotFullRefresh, MarketDataIncrementalRefresh |
| 279 | MDUpdateAction | MarketDataIncrementalRefresh |
| 320 | SecurityReqID | SecurityListRequest, SecurityList, SecurityDefinitionRequest, SecurityDefinition |
| 322 | SecurityResponseID | SecurityDefinition |
| 325 | UnsolicitedIndicator | PositionReport |
| 372 | RefMsgType | BusinessMessageReject |
| 373 | SessionRejectReason | Reject |
| 380 | BusinessRejectReason | BusinessMessageReject |
| 393 | TotNoRelatedSym | SecurityDefinition |
| 434 | CxlRejResponseTo | OrderCancelReject |
| 487 | TradeReportTransType | TradeCaptureReport |
| 553 | Username | Logon |
| 554 | Password | Logon |
| 559 | SecurityListRequestType | SecurityListRequest |
| 568 | TradeRequestID | TradeCaptureReportRequest, TradeCaptureReport, TradeCaptureReportRequestAck |
| 569 | TradeRequestType | (accepted, not enforced) |
| 571 | TradeReportID | TradeCaptureReport |
| 584 | MassStatusReqID | (accepted, not enforced) |
| 585 | MassStatusReqType | (accepted, not enforced — no scoping beyond Symbol/Side) |
| 702 | NoPositions | PositionReport |
| 703 | PosType | PositionReport |
| 704 | LongQty | PositionReport |
| 705 | ShortQty | PositionReport |
| 707 | PosAmtType | PositionReport |
| 708 | PosAmt | PositionReport |
| 710 | PosReqID | RequestForPositions, PositionReport |
| 715 | ClearingBusinessDate | PositionReport |
| 721 | PosMaintRptID | PositionReport |
| 724 | PosReqType | (accepted, not enforced) |
| 727 | TotalNumPosReports | PositionReport |
| 728 | PosReqResult | PositionReport |
| 748 | TotNumTradeReports | TradeCaptureReport, TradeCaptureReportRequestAck |
| 753 | NoPosAmt | PositionReport |
| 912 | LastRptRequested | TradeCaptureReport, TradeCaptureReportRequestAck |
| 969 | MinPriceIncrement | SecurityDefinition |
| 1023 | MDPriceLevel | MarketDataIncrementalRefresh (full-depth entries only) |
| 1138 | DisplayQty | NewOrderSingle, ExecutionReport |
| 1148 | LowLimitPrice | SecurityDefinition |
| 1149 | HighLimitPrice | SecurityDefinition |
Appendix B — Enumerations Reference
OrdType (40)
| Value | Meaning |
|---|---|
1 |
Market |
2 |
Limit (also Limit+IOC when combined with TimeInForce=3) |
3 |
Stop |
4 |
Stop Limit |
TimeInForce (59)
| Value | Meaning | Supported? |
|---|---|---|
0 |
Day | ✅ (default) |
1 |
GTC | ❌ rejected |
2 |
At the Opening | ❌ rejected |
3 |
IOC | ✅ (native FAK) |
4 |
FOK | ❌ rejected |
5 |
GTX | ❌ rejected |
6 |
GTD | ❌ rejected |
Side (54)
| Value | Meaning |
|---|---|
1 |
Buy |
2 |
Sell |
ExecType (150) / OrdStatus (39)
See the full table with context in section 5. Codes used: A PendingNew, 0 New, 6 PendingCancel, 4 Canceled, E PendingReplace, 5 Replaced, 8 Rejected, 2 Filled, 1 PartiallyFilled.
OrdRejReason (103)
Present on ExecutionReport only when ExecType=8. Two different sources feed this tag:
- Gateway pre-trade check (synchronous, on your own
NewOrderSingle): can currently produce1,3,11,13, or16from the table below, or0as a generic fallback for anything else the check catches. - Exchange itself, asynchronously (a
NewOrderSinglethat was accepted and forwarded, or a cancel/replace the exchange later rejects — see the note in section 5): passes through the exchange's own reason code directly and unclamped. The exchange doesn't publish a single standard value table for this field (its real reject-code references are organized per market segment), so treat any value here as the exchange's own code, not necessarily one of the standard FIX values below.
The table below is the standard FIX 4.4 enum, useful as a reference for the gateway pre-trade check values above — it is not an exhaustive list of what the exchange itself may send asynchronously:
| Value | Meaning |
|---|---|
0 |
Broker / Exchange Option |
1 |
Unknown symbol |
2 |
Exchange closed |
3 |
Order exceeds limit |
4 |
Too late to enter |
5 |
Unknown Order |
6 |
Duplicate Order |
7 |
Duplicate of a verbally communicated order |
8 |
Stale Order |
9 |
Trade Along required |
10 |
Invalid Investor ID |
11 |
Unsupported order characteristic |
12 |
Surveillance Option |
13 |
Incorrect quantity |
14 |
Incorrect allocated quantity |
15 |
Unknown account(s) |
16 |
Price exceeds current price band |
18 |
Non-displayed limit order not allowed on this market |
19 |
Order not risk-tested |
99 |
Other |
CxlRejReason (102)
Present on OrderCancelReject. Unlike OrdRejReason above, this tag is only ever produced synchronously by the gateway/account (a rejected cancel/replace that the exchange itself rejects asynchronously surfaces as an ExecutionReport with OrdRejReason instead — see the note in section 5).
| Value | Meaning | Actually sent by this gateway? |
|---|---|---|
0 |
Too late to cancel | ✅ |
1 |
Unknown order | ✅ (both a session-scoped lookup miss, and a genuine backend "order not found") |
2 |
Broker / Exchange Option | ✅ (generic fallback for a backend rejection not covered by another value) |
3 |
Order already in Pending Cancel or Pending Replace status | ✅ |
4 |
Unable to process Order Mass Cancel Request | not currently produced |
5 |
OrigOrdModTime did not match last TransactTime of order | not currently produced |
6 |
Duplicate ClOrdID received | ✅ |
99 |
Other | ✅ (VIEWER-role rejects, and any backend reason not covered above — see Text(58)) |
BusinessRejectReason (380)
| Value | Meaning | Actually sent by this gateway? |
|---|---|---|
0 |
Other | ✅ (fallback for an unrecognized MsgType, see 3 below) |
1 |
Unknown ID | ✅ (OrderStatusRequest for an unknown order) |
2 |
Unknown Security | ✅ (unknown Symbol on NewOrderSingle) |
3 |
Unsupported Message Type | ✅ (default for any unrecognized MsgType) |
4 |
Application not available | not currently produced |
5 |
Conditionally Required Field Missing | not currently produced |
6 |
Not authorized | ✅ (VIEWER role on OrderMassCancelRequest) |
7 |
DeliverTo firm not available at this time | not currently produced |
| (other values) | Exchange-defined (throttle limits, kill switch, risk/credit controls, etc.) | ✅ — an unsolicited exchange-side error tied to an order passes the exchange's own code through directly, unclamped; values above 7 are the exchange's own, not a standard FIX 4.4 code. |
SessionRejectReason (373)
Only value 99 (Other) is currently produced by this gateway, for any application message received before Logon completes. The full standard enum exists for completeness if your engine validates against it, but no other value will be sent.
SecurityType (167)
| Value | Meaning |
|---|---|
FUT |
Future |
OPT |
Option |
MLEG |
Multi-leg / spread |
PosType (703) / PosAmtType (707)
PosType is always "TOT" (Total Transaction Qty). PosAmtType is always "FMTM" (Final Mark-to-Market).
MDEntryType (269)
| Value | Meaning |
|---|---|
0 |
Bid |
1 |
Offer |
MDUpdateAction (279)
| Value | Meaning |
|---|---|
0 |
New |
1 |
Change |
2 |
Delete |