FIX: the language of electronic trading
The Financial Information eXchange protocol is how brokers, funds, banks and exchanges have sent orders and fills to each other since the 1990s. If you work anywhere near trading systems, you will read FIX messages. This page covers the message format, the order lifecycle, the session layer that makes it reliable, and how it relates to SBE and to Prayog's own commands.
1. What FIX is
FIX is an open standard run by the FIX Trading Community. It defines two things:
- Business messages with agreed meanings: "new order" (
NewOrderSingle), "cancel this" (OrderCancelRequest), "here's what happened to your order" (ExecutionReport), plus market data, allocations and more. - A session protocol that delivers those messages exactly once and in order over an unreliable connection: logon, heartbeats, sequence numbers and resending gaps.
Before FIX, every broker had its own format, often over the phone. FIX let one buy-side system talk to dozens of brokers with one vocabulary.
2. A business example: a numbered phone order line
A fund manager phones a broker. Both sides keep a numbered notebook:
- "Message 41: buy 100 INFY at 1,500, my reference A-17." The broker writes it down as message 41.
- If the broker's last entry was 39, they say: "I'm missing 40, please repeat it." That's a ResendRequest.
- Every few seconds of silence: "Still there?" "Yes." Those are Heartbeats and TestRequests.
- The broker calls back: "A-17: filled 60 at 1,500, 40 still working." That's an ExecutionReport.
FIX is that notebook discipline, made machine-readable.
3. Anatomy of a message
Classic FIX ("tag=value") is text: a list of tag=value fields separated by the SOH control character (byte 1, shown here as |). Each tag is a number with an agreed meaning.
8=FIX.4.4|9=126|35=D|34=41|49=FUND1|52=20261005-04:00:00.000|56=BROKER|11=A-17|21=1|55=INFY|54=1|60=20261005-04:00:00.000|38=100|40=2|44=1500|10=131|
| Part | Tags | Meaning |
|---|---|---|
| Header | 8 BeginString, 9 BodyLength, 35 MsgType, 49/56 sender and target, 34 MsgSeqNum, 52 SendingTime | Who, to whom, which message number, what kind. 8, 9 and 35 must come first, in that order. |
| Body | 11 ClOrdID, 55 Symbol, 54 Side (1 buy, 2 sell), 38 OrderQty, 40 OrdType (1 market, 2 limit), 44 Price | The business content |
| Trailer | 10 CheckSum | Sum of every byte before 10=, modulo 256, as three digits |
BodyLength (9) counts the bytes from just after the 9=...| field up to and including the | before 10=. With it, a reader knows exactly where the message ends without scanning for the trailer.
| MsgType (35) | Name | Direction |
|---|---|---|
D | NewOrderSingle | client → exchange |
F | OrderCancelRequest | client → exchange |
G | OrderCancelReplaceRequest (modify) | client → exchange |
8 | ExecutionReport | exchange → client |
9 | OrderCancelReject | exchange → client |
A, 0, 1, 2, 4, 5 | Logon, Heartbeat, TestRequest, ResendRequest, SequenceReset, Logout | session layer, both ways |
4. Build a message
Fill in a NewOrderSingle. The BodyLength and CheckSum are computed the way a FIX engine would.
5. An order's life in FIX
Every reply to an order is an ExecutionReport (35=8). Its ExecType (150) says what just happened; OrdStatus (39) says where the order stands now.
| ExecType (150) | Meaning | Prayog event |
|---|---|---|
0 New | Order accepted and working | OrderAccepted |
F Trade | A (partial) fill | Trade |
4 Canceled | Open quantity removed | OrderCancelled |
5 Replaced | Modify accepted | OrderModified |
8 Rejected | Order refused | OrderRejected |
C Expired | Day order ended at the close | OrderCancelled with reason EXPIRED |
Three quantities always add up: OrderQty = CumQty + LeavesQty while the order is alive (filled so far + still open). Prayog's OrderModified.leavesQuantity is the same idea.
6. The session layer: exactly once, in order
TCP already delivers bytes in order, so why sequence numbers? Because connections drop, and when you reconnect TCP has forgotten everything. FIX sessions outlive connections:
- Logon (35=A) with a heartbeat interval (
108). Each side states its next sequence number. - Every message carries
MsgSeqNum(34), one higher than the last, per direction, for the whole trading day. - Gap detected (expected 40, got 42): send ResendRequest (35=2) with
BeginSeqNo=40,EndSeqNo=41. - The other side resends 40 and 41 with
PossDupFlag(43)=Y, so the receiver can drop copies it already has. Session-level messages that are not worth resending (old heartbeats) are skipped with SequenceReset-GapFill (35=4, 123=Y). - Heartbeat (35=0) when idle; TestRequest (35=1) if the other side goes quiet. No answer means the link is dead.
7. The FIX family
| Layer | Standard | What it is |
|---|---|---|
| Business messages | FIX application messages (4.2, 4.4, 5.0 SP2) | NewOrderSingle, ExecutionReport... the vocabulary |
| Encoding | tag=value | The classic text form shown above |
| Encoding | SBE | Fixed-layout binary, for low latency |
| Encoding | FAST | Compressed, mostly for older market data feeds |
| Session | FIX session (FIXT 1.1) | Logon, sequence numbers, resend, over TCP |
| Session | FIXP | A lighter session protocol designed for binary encodings like SBE |
Low-latency venues usually offer a binary order-entry protocol next to (or instead of) text FIX, because parsing text costs microseconds. CME's iLink 3, for example, is SBE over FIXP. Many venues design their own binary protocols; the concepts (client order ids, execution reports, sequence numbers, resend) stay the same.
8. FIX and Prayog
Prayog does not speak FIX on the wire; its gateway (S10) will use JSON over HTTP and WebSocket so bots in any language are easy to write. But its commands and events follow FIX's model, so the mapping is direct:
| FIX | Prayog |
|---|---|
| NewOrderSingle (D): ClOrdID, Symbol, Side, OrdType, OrderQty, Price | NewOrder(clientOrderId, accountId, symbol, side, type, price, quantity) |
| OrderCancelRequest (F) | CancelOrder (by exchange order id rather than OrigClOrdID) |
| OrderCancelReplaceRequest (G): OrderQty is the new total | ModifyOrder: quantity is the new total, as in FIX (ADR 0004) |
| ExecutionReport (8) | OrderAccepted, Trade, OrderCancelled, OrderModified, OrderRejected |
| MsgSeqNum + resend | Input sequence and event seq; the journal makes any range recoverable |
| Price as decimal text | Price as integer paise. Never a float. |
9. Parse a message
Paste any tag=value message (with | or real SOH separators). The parser names common tags and checks BodyLength and CheckSum. The sample is a partial fill whose checksum is deliberately wrong (the real value is 038); press "Fix" to recompute it, or edit a field and watch both checks fail.
10. Questions
Why does every request need a new ClOrdID?
So every message is uniquely identifiable and replies can be matched to requests. A cancel has its own ClOrdID, and OrigClOrdID says which order it targets. Prayog rejects a reused client order id (DUPLICATE_CLIENT_ORDER_ID) for the same reason.
Why is the checksum so weak?
A sum of bytes modulo 256 catches truncation and framing mistakes, not deliberate tampering or every bit flip; TCP and TLS handle those. Prayog's journal uses CRC32C, which is much stronger, because it guards against torn disk writes.
Is FIX used for market data too?
Yes (MarketDataSnapshot, MarketDataIncrementalRefresh), but high-volume feeds are usually binary and multicast, such as SBE over UDP, because one text message per book change is too slow.