Chibuzor Daniel Uwezukwe

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.

A Brief History

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.

Message Structure: Tag-Value Pairs

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.

The Three Sections of Every FIX Message

Every FIX message has a standard header, a body, and a trailer:

SectionTagsPurpose
Header8, 9, 35, 49, 56, 34, 52Protocol version, message type, routing, sequence number, timestamp
BodyMessage-type specificThe actual business data: symbol, side, quantity, price, etc.
Trailer10Checksum for integrity verification

Key Header Tags

TagNameExample
8BeginStringFIX.4.2
9BodyLength178 (byte count from tag 35 to tag 10, exclusive)
35MsgTypeD (NewOrderSingle), 8 (ExecutionReport), 0 (Heartbeat)
49SenderCompIDCLIENT (who sent the message)
56TargetCompIDBROKER (who should receive it)
34MsgSeqNum1 (increments with every message, used for gap detection)
52SendingTime20241015-10:30:00.000 (UTC, YYYYMMDD-HH:MM:SS.sss)

The Session Layer

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).

Logon (35=A)

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.

Heartbeat (35=0) and TestRequest (35=1)

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|

Sequence Number Gap Detection and ResendRequest (35=2)

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.

Logout (35=5)

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.

Application Messages

NewOrderSingle (35=D)

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.

ExecutionReport (35=8)

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 Values Reference

ExecType (150)OrdStatus (39)Meaning
00New: order acknowledged, nothing filled yet
11Partial Fill: some quantity filled
22Fill: order completely filled
44Canceled: order successfully cancelled
55Replaced: order amended
88Rejected: broker rejected the order
CCExpired: order expired (e.g. day order at close)

OrderCancelRequest (35=F)

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|

OrderCancelReplaceRequest (35=G)

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|

The Checksum (Tag 10)

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.

BodyLength (Tag 9)

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
}

Repeating Groups

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.

Building a FIX Parser in TypeScript

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, '|'));

FIX in DeFi Bridges

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:

  1. An institution's OMS sends a NewOrderSingle (35=D) over a FIX session to the bridge endpoint.
  2. The bridge parses the FIX message, maps FIX fields to on-chain order parameters (symbol to asset pair, quantity to token amount, price to limit price in base units), and submits a transaction to the Cosmos chain.
  3. On execution or rejection, the bridge emits an ExecutionReport (35=8) back to the OMS with the appropriate ExecType and on-chain transaction hash embedded in a custom tag.
  4. Sequence numbers and session state are maintained in Redis so the bridge survives restarts without losing session continuity.

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.

Key Takeaways