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:

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:

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|
PartTagsMeaning
Header8 BeginString, 9 BodyLength, 35 MsgType, 49/56 sender and target, 34 MsgSeqNum, 52 SendingTimeWho, to whom, which message number, what kind. 8, 9 and 35 must come first, in that order.
Body11 ClOrdID, 55 Symbol, 54 Side (1 buy, 2 sell), 38 OrderQty, 40 OrdType (1 market, 2 limit), 44 PriceThe business content
Trailer10 CheckSumSum 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)NameDirection
DNewOrderSingleclient → exchange
FOrderCancelRequestclient → exchange
GOrderCancelReplaceRequest (modify)client → exchange
8ExecutionReportexchange → client
9OrderCancelRejectexchange → client
A, 0, 1, 2, 4, 5Logon, Heartbeat, TestRequest, ResendRequest, SequenceReset, Logoutsession 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.

Client (broker / bot)Exchange 35=D NewOrderSingle, ClOrdID A-17, buy 100 @ 1500 35=8 ExecType 0 New, OrderID 9001, OrdStatus New 35=8 ExecType F Trade, LastQty 60, LastPx 1500, CumQty 60, LeavesQty 40 35=F OrderCancelRequest, ClOrdID A-18, OrigClOrdID A-17 35=8 ExecType 4 Canceled, CumQty 60, LeavesQty 0 Every request gets a new ClOrdID; OrigClOrdID points at the order being changed.
ExecType (150)MeaningPrayog event
0 NewOrder accepted and workingOrderAccepted
F TradeA (partial) fillTrade
4 CanceledOpen quantity removedOrderCancelled
5 ReplacedModify acceptedOrderModified
8 RejectedOrder refusedOrderRejected
C ExpiredDay order ended at the closeOrderCancelled 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:

  1. Logon (35=A) with a heartbeat interval (108). Each side states its next sequence number.
  2. Every message carries MsgSeqNum (34), one higher than the last, per direction, for the whole trading day.
  3. Gap detected (expected 40, got 42): send ResendRequest (35=2) with BeginSeqNo=40, EndSeqNo=41.
  4. 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).
  5. Heartbeat (35=0) when idle; TestRequest (35=1) if the other side goes quiet. No answer means the link is dead.
This is the same idea as Prayog's input sequence and journal: number everything, store it, and anything can be recovered by replaying from a number. A FIX engine must persist its outgoing messages so it can resend them after a crash, which makes it a journal too.

7. The FIX family

LayerStandardWhat it is
Business messagesFIX application messages (4.2, 4.4, 5.0 SP2)NewOrderSingle, ExecutionReport... the vocabulary
Encodingtag=valueThe classic text form shown above
EncodingSBEFixed-layout binary, for low latency
EncodingFASTCompressed, mostly for older market data feeds
SessionFIX session (FIXT 1.1)Logon, sequence numbers, resend, over TCP
SessionFIXPA 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:

FIXPrayog
NewOrderSingle (D): ClOrdID, Symbol, Side, OrdType, OrderQty, PriceNewOrder(clientOrderId, accountId, symbol, side, type, price, quantity)
OrderCancelRequest (F)CancelOrder (by exchange order id rather than OrigClOrdID)
OrderCancelReplaceRequest (G): OrderQty is the new totalModifyOrder: quantity is the new total, as in FIX (ADR 0004)
ExecutionReport (8)OrderAccepted, Trade, OrderCancelled, OrderModified, OrderRejected
MsgSeqNum + resendInput sequence and event seq; the journal makes any range recoverable
Price as decimal textPrice 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.