FIX Protocol: The Language of Electronic Trading
2026-10-07
FIX (Financial Information eXchange) is the messaging standard that powers electronic trading across global capital markets. Broker-dealers, exchanges, asset managers, and trading systems communicate order flow using FIX. If a hedge fund sends a buy order to a prime broker, if a market maker quotes a price to an institutional client, or if an exchange reports an execution back to a trading algorithm, there is a high probability that message is encoded in FIX.
Understanding FIX is essential if you are building any system that touches institutional finance: order management systems, execution management systems, algo trading engines, smart order routers, or DeFi bridges to traditional finance infrastructure.
FIX was created in 1992 by Fidelity Investments and Salomon Brothers to replace phone-based order communication between buy-side and sell-side firms. Before FIX, a portfolio manager would call a broker to place an order verbally, which was slow, error-prone, and impossible to audit at scale. FIX standardised the message format so any two firms could connect their systems and exchange orders electronically without custom bilateral integrations.
The protocol has evolved through several major versions: FIX 4.0, 4.1, 4.2, 4.4, and then the split into FIXT 1.1 (transport) and FIX 5.0 SP2 (application). FIX 4.2 remains the most widely deployed version in production systems today, despite being published in 2000, because financial infrastructure is extraordinarily conservative about upgrades.
Every FIX message is a sequence of tag=value pairs separated by the SOH character (ASCII 0x01, often written as | in documentation). There is no JSON, no XML, no schema validation at the wire level. Just raw byte sequences.
8=FIX.4.2|9=178|35=D|49=CLIENT|56=BROKER|34=1|52=20241015-10:30:00.000|
11=ORDER001|55=AAPL|54=1|38=100|40=2|44=150.00|59=0|10=128|
Each number before the = is a tag. Each tag has a fixed semantic meaning defined by the FIX specification. The value after = is the field value. The message above is a NewOrderSingle (tag 35 = D) asking to buy 100 shares of AAPL at a limit price of $150.00.
Every FIX message has a standard header, a body, and a trailer:
| Section | Tags | Purpose |
|---|---|---|
| Header | 8, 9, 35, 49, 56, 34, 52 | Protocol version, message type, routing, sequence number, timestamp |
| Body | Message-type specific | The actual business data: symbol, side, quantity, price, etc. |
| Trailer | 10 | Checksum for integrity verification |
| Tag | Name | Example |
|---|---|---|
| 8 | BeginString | FIX.4.2 |
| 9 | BodyLength | 178 (byte count from tag 35 to tag 10, exclusive) |
| 35 | MsgType | D (NewOrderSingle), 8 (ExecutionReport), 0 (Heartbeat) |
| 49 | SenderCompID | CLIENT (who sent the message) |
| 56 | TargetCompID | BROKER (who should receive it) |
| 34 | MsgSeqNum | 1 (increments with every message, used for gap detection) |
| 52 | SendingTime | 20241015-10:30:00.000 (UTC, YYYYMMDD-HH:MM:SS.sss) |
FIX has two distinct layers. The session layer manages the connection lifecycle: establishing connectivity, keeping it alive, detecting gaps, and tearing it down gracefully. The application layer carries the actual business messages (orders, executions, quotes).
Every FIX session begins with a Logon. The initiator sends it, the acceptor responds with its own Logon to confirm the connection. Both sides reset or continue sequence numbers depending on the ResetOnLogon configuration.
8=FIX.4.2|9=73|35=A|49=CLIENT|56=BROKER|34=1|52=20241015-09:00:00.000|
98=0|108=30|10=062|
Tag 98 (EncryptMethod) = 0 means no encryption. Tag 108 (HeartBtInt) = 30 means the session expects a heartbeat every 30 seconds.
If no messages are exchanged within the heartbeat interval, the sender must emit a Heartbeat to prove the connection is alive. If a peer suspects the connection is dead, it sends a TestRequest with a TestReqID. The counterparty must respond with a Heartbeat echoing that ID. A missing response within a defined timeout triggers a session disconnect and gap fill procedure.
// TestRequest
8=FIX.4.2|9=65|35=1|49=BROKER|56=CLIENT|34=102|52=20241015-10:00:00.000|
112=TEST_1|10=215|
// Expected Heartbeat response
8=FIX.4.2|9=65|35=0|49=CLIENT|56=BROKER|34=89|52=20241015-10:00:00.500|
112=TEST_1|10=198|
Every message carries a MsgSeqNum (tag 34) that increments by one. If a receiver gets sequence number 105 after expecting 103, it knows messages 103 and 104 were lost. It sends a ResendRequest asking for the gap to be replayed:
8=FIX.4.2|9=68|35=2|49=CLIENT|56=BROKER|34=105|52=20241015-10:05:00.000|
7=103|16=104|10=071|
Tag 7 (BeginSeqNo) = 103, tag 16 (EndSeqNo) = 104. The counterparty replays those messages with a PossDupFlag (tag 43 = Y) set so the receiver knows they are retransmissions, not new messages.
A graceful session termination. Either party can initiate. The counterparty acknowledges with its own Logout. If a Logout is received with no prior Logon, or if a connection drops without Logout, it is an abnormal termination.
The most fundamental order message. Sent by a buy-side client to a broker to submit a new order.
8=FIX.4.2|9=201|35=D|49=BUYSIDE|56=SELLSIDE|34=42|52=20241015-14:30:00.123|
11=ORD-20241015-001| // ClOrdID: client-assigned unique order ID
55=MSFT| // Symbol
54=1| // Side: 1=Buy, 2=Sell
38=500| // OrderQty
40=2| // OrdType: 1=Market, 2=Limit, 3=Stop
44=415.50| // Price (required for limit orders)
59=0| // TimeInForce: 0=Day, 1=GTC, 3=IOC, 4=FOK
60=20241015-14:30:00.123| // TransactTime
10=244|
Key fields for a limit order: ClOrdID (your reference, must be unique per session), Symbol, Side, OrderQty, OrdType = 2 (limit), Price, and TimeInForce.
The broker's response to any order event: acknowledgement, partial fill, full fill, rejection, cancellation confirmation. Every order lifecycle event generates an ExecutionReport. The ExecType (tag 150) and OrdStatus (tag 39) fields tell you what happened.
// Order acknowledged (pending new)
8=FIX.4.2|9=215|35=8|49=SELLSIDE|56=BUYSIDE|34=88|52=20241015-14:30:00.250|
37=BROKER-98765| // OrderID: broker's internal order ID
11=ORD-20241015-001| // ClOrdID: echoed from the NewOrderSingle
17=EXEC-001| // ExecID: unique per execution event
150=0| // ExecType: 0=New (acknowledged)
39=0| // OrdStatus: 0=New
55=MSFT|
54=1|
38=500| // OrderQty
14=0| // CumQty: total filled so far
151=500| // LeavesQty: remaining open quantity
6=0| // AvgPx: average fill price
10=187|
// Partial fill: 200 shares filled at 415.48
8=FIX.4.2|9=230|35=8|49=SELLSIDE|56=BUYSIDE|34=91|52=20241015-14:30:15.780|
37=BROKER-98765|
11=ORD-20241015-001|
17=EXEC-002|
150=1| // ExecType: 1=Partial fill
39=1| // OrdStatus: 1=Partially filled
55=MSFT|
54=1|
32=200| // LastQty: this fill quantity
31=415.48| // LastPx: this fill price
38=500|
14=200| // CumQty: 200 filled total
151=300| // LeavesQty: 300 remaining
6=415.48| // AvgPx: average price so far
10=201|
| ExecType (150) | OrdStatus (39) | Meaning |
|---|---|---|
| 0 | 0 | New: order acknowledged, nothing filled yet |
| 1 | 1 | Partial Fill: some quantity filled |
| 2 | 2 | Fill: order completely filled |
| 4 | 4 | Canceled: order successfully cancelled |
| 5 | 5 | Replaced: order amended |
| 8 | 8 | Rejected: broker rejected the order |
| C | C | Expired: order expired (e.g. day order at close) |
To cancel a live order, the client sends an OrderCancelRequest referencing both the original ClOrdID and the broker's OrderID. The broker responds with an ExecutionReport with ExecType = 4 (Canceled) on success, or an OrderCancelReject (35=9) on failure.
8=FIX.4.2|9=178|35=F|49=BUYSIDE|56=SELLSIDE|34=55|52=20241015-14:35:00.000|
41=ORD-20241015-001| // OrigClOrdID: the ClOrdID of the order to cancel
11=CANCEL-001| // ClOrdID: new unique ID for this cancel request
37=BROKER-98765| // OrderID: broker's ID
55=MSFT|
54=1|
38=500|
60=20241015-14:35:00.000|
10=143|
Used to amend a live order's quantity or price without cancelling and resubmitting. Called a "cancel-replace" because under the hood the broker cancels the original and creates a new order, but the client experiences it as a single atomic amendment. The order loses queue priority at the exchange.
8=FIX.4.2|9=195|35=G|49=BUYSIDE|56=SELLSIDE|34=57|52=20241015-14:36:00.000|
41=ORD-20241015-001| // OrigClOrdID
11=REPLACE-001| // New ClOrdID for this replace request
37=BROKER-98765|
55=MSFT|
54=1|
38=300| // New quantity (reduced from 500)
40=2|
44=415.00| // New price (lowered from 415.50)
59=0|
60=20241015-14:36:00.000|
10=199|
Tag 10 is always the last field. Its value is the sum of all ASCII byte values in the message (from tag 8 through the SOH after tag 9's value, inclusive of all SOH separators), modulo 256, formatted as a zero-padded three-digit string.
function calculateChecksum(message: string): string {
let sum = 0;
for (let i = 0; i < message.length; i++) {
sum += message.charCodeAt(i);
}
return String(sum % 256).padStart(3, '0');
}
The checksum is a basic integrity check, not a cryptographic one. It catches transmission corruption but not deliberate tampering. FIX assumes transport-layer security (TLS) handles confidentiality and authentication.
Tag 9 counts the number of bytes from the start of tag 35 to the SOH that terminates tag 10's value, inclusive. It does not include tags 8 and 9 themselves. Miscalculating BodyLength is one of the most common implementation bugs in FIX engines, causing counterparties to reject your messages with a session-level reject.
function calculateBodyLength(message: string): number {
// Message format: 8=FIX.4.29=NNN[BODY]10=NNN
const bodyStart = message.indexOf('\x0135=');
// Include everything from tag 35 to end of tag 10 value (including final SOH)
return message.length - bodyStart - 1; // -1 for opening SOH before 35
}
FIX supports repeating groups for messages that carry multiple instances of a sub-structure, such as a list of legs in a multi-leg order, or multiple bid/ask levels in a market data snapshot. The group is preceded by a count tag, and the first tag in the group definition acts as the delimiter between instances.
// MarketDataSnapshotFullRefresh with 2 price levels
8=FIX.4.2|9=...|35=W|...|
268=2| // NoMDEntries: 2 entries follow
269=0| // MDEntryType: 0=Bid
270=414.90| // MDEntryPx
271=1000| // MDEntrySize
269=1| // MDEntryType: 1=Offer (this tag restarts the group)
270=415.10|
271=800|
10=...|
The parser knows a new group entry begins each time it encounters tag 269 again. Repeating groups must be parsed in order. Random-access parsing of FIX messages is not valid.
A minimal parser that converts a raw FIX string into a map of tag-value pairs:
const SOH = '\x01';
function parseFixMessage(raw: string): Map<number, string> {
const fields = new Map<number, string>();
const pairs = raw.split(SOH).filter(Boolean);
for (const pair of pairs) {
const eq = pair.indexOf('=');
if (eq === -1) continue;
const tag = parseInt(pair.substring(0, eq), 10);
const value = pair.substring(eq + 1);
fields.set(tag, value);
}
return fields;
}
function buildFixMessage(
msgType: string,
senderCompId: string,
targetCompId: string,
seqNum: number,
body: Map<number, string>,
): string {
const timestamp = new Date()
.toISOString()
.replace(/[-:T]/g, '')
.replace(/\.\d+Z$/, '')
.replace(/(\d{8})(\d{6})/, '$1-$2');
// Build body section (tags 35 onward)
let bodyStr = `35=${msgType}${SOH}`;
bodyStr += `49=${senderCompId}${SOH}`;
bodyStr += `56=${targetCompId}${SOH}`;
bodyStr += `34=${seqNum}${SOH}`;
bodyStr += `52=${timestamp}.000${SOH}`;
for (const [tag, value] of body) {
bodyStr += `${tag}=${value}${SOH}`;
}
const bodyLength = bodyStr.length;
// Prepend standard header
let message = `8=FIX.4.2${SOH}9=${bodyLength}${SOH}${bodyStr}`;
// Append checksum
let sum = 0;
for (let i = 0; i < message.length; i++) {
sum += message.charCodeAt(i);
}
const checksum = String(sum % 256).padStart(3, '0');
message += `10=${checksum}${SOH}`;
return message;
}
// Usage: build a NewOrderSingle
const orderBody = new Map<number, string>([
[11, 'ORD-001'], // ClOrdID
[55, 'AAPL'], // Symbol
[54, '1'], // Side: Buy
[38, '100'], // OrderQty
[40, '2'], // OrdType: Limit
[44, '190.50'], // Price
[59, '0'], // TimeInForce: Day
[60, '20241015-14:30:00.000'],
]);
const msg = buildFixMessage('D', 'CLIENT', 'BROKER', 1, orderBody);
console.log(msg.replace(/\x01/g, '|'));
One interesting application of FIX outside traditional finance is bridging on-chain trading systems to institutional order management systems. In my work on Re Protocol at Cypher Core, we built a FIX bridge on top of the Cosmos SDK that allowed institutional participants to submit orders to an on-chain DEX using standard FIX messages, without needing to understand blockchain primitives directly.
The architecture looked roughly like this:
NewOrderSingle (35=D) over a FIX session to the bridge endpoint.ExecutionReport (35=8) back to the OMS with the appropriate ExecType and on-chain transaction hash embedded in a custom tag.This pattern makes on-chain venues accessible to the billions of dollars of institutional flow that is entirely FIX-native and will not adopt new APIs without years of compliance review.
ClOrdID must be unique per session per order. OrigClOrdID chains cancel and replace requests to the original order.ClOrdID, OrderID, and Symbol in every ExecutionReport. OMS systems use these to correlate events to their internal order records.