FIX Protocol Introduction

"FIX is the HTTP of finance - you don't need to fully understand it, but you need to know how it affects your orders."


1. What is FIX Protocol?

1.1 Definition

FIX (Financial Information eXchange): A financial information exchange protocol that serves as the industry-standard communication protocol for electronic trading.

Your Trading System <──FIX Messages──> Broker/Exchange

Send: "I want to buy 100 shares of AAPL, limit price $185"
Receive: "Order received, ID 12345"
Receive: "50 shares filled at $185.00"
Receive: "Remaining 50 shares filled at $185.01"

1.2 Why Do We Need FIX?

ProblemWithout FIXWith FIX
Connect to new brokerRedevelop integrationJust change config
Multi-broker tradingN sets of code1 set of code
Order status syncDifferent formats per brokerStandardized messages
TroubleshootingDifferent logs per brokerUnified protocol analysis

1.3 FIX Protocol Versions

VersionRelease YearMain Use
FIX 4.01996Legacy version
FIX 4.22000Still in use
FIX 4.42003Most common
FIX 5.02006New features
FIXT 1.12008Transport layer separation

Most brokers and exchanges support FIX 4.4.


2. FIX Message Structure

2.1 Message Format

FIX messages are sequences of key-value pairs, separated by SOH (ASCII 01):

8=FIX.4.4|9=176|35=D|49=SENDER|56=TARGET|34=2|52=20240101-09:30:00.000|
11=ORD001|21=1|55=AAPL|54=1|60=20240101-09:30:00.000|38=100|40=2|44=185.00|
59=0|10=123|

Human-readable version:

8=FIX.4.4        # Protocol version
9=176            # Body length
35=D             # Message type (D=New Order)
49=SENDER        # Sender ID
56=TARGET        # Target ID
34=2             # Message sequence number
52=20240101-09:30:00.000  # Sending time
11=ORD001        # Client Order ID
21=1             # Handling instruction (1=automated)
55=AAPL          # Symbol
54=1             # Side (1=Buy)
60=20240101-09:30:00.000  # Transaction time
38=100           # Order quantity
40=2             # Order type (2=Limit)
44=185.00        # Limit price
59=0             # Time in force (0=Day)
10=123           # Checksum

2.2 Message Layers

+-------------------------------------------------------------+
|                     FIX Message Structure                    |
+-------------------------------------------------------------+
|                                                             |
|  +-----------------------------------------------------+   |
|  | Header                                               |   |
|  |   8=Version  9=Length  35=Type  49=Sender  56=Target |   |
|  |   34=SeqNum  52=Time                                 |   |
|  +-----------------------------------------------------+   |
|                            |                               |
|  +-----------------------------------------------------+   |
|  | Body                                                 |   |
|  |   Varies by message type (Tag 35)                    |   |
|  |   Order msg: 55=Symbol 54=Side 38=Qty 44=Price...    |   |
|  +-----------------------------------------------------+   |
|                            |                               |
|  +-----------------------------------------------------+   |
|  | Trailer                                              |   |
|  |   10=Checksum                                        |   |
|  +-----------------------------------------------------+   |
|                                                             |
+-------------------------------------------------------------+

3. Core Message Types

3.1 Session Layer Messages

MsgType (35)NamePurpose
ALogonEstablish session
5LogoutDisconnect session
0HeartbeatConnection health check
1TestRequestTest connection
2ResendRequestRequest message resend
4SequenceResetReset sequence number
MsgType (35)NameDirectionPurpose
DNewOrderSingleClient -> BrokerSubmit new order
FOrderCancelRequestClient -> BrokerCancel order
GOrderCancelReplaceRequestClient -> BrokerModify order
8ExecutionReportBroker -> ClientOrder status/fill report
9OrderCancelRejectBroker -> ClientCancel rejection

3.3 Market Data Messages

MsgType (35)NamePurpose
VMarketDataRequestSubscribe to market data
WMarketDataSnapshotFullRefreshMarket data snapshot
XMarketDataIncrementalRefreshMarket data incremental update

4. Key Tags Explained

4.1 Side (Tag 54)

ValueMeaning
1Buy
2Sell
5Sell Short
6Sell Short Exempt

4.2 Order Type (Tag 40 - OrdType)

ValueMeaningRequired Tags
1MarketNone
2Limit44=Price
3Stop99=Stop Price
4Stop Limit44=Limit Price, 99=Stop Price
PPeggedSpecific fields

4.3 Time in Force (Tag 59)

ValueMeaningDescription
0DayValid for current day
1GTCGood Till Cancel
2OPGAt the Opening
3IOCImmediate or Cancel
4FOKFill or Kill
6GTDGood Till Date

4.4 Order Status (Tag 39 - OrdStatus)

ValueMeaning
0New (Accepted)
1Partially Filled
2Filled
4Canceled
8Rejected
CExpired

4.5 Execution Type (Tag 150 - ExecType)

ValueMeaning
0New (Order confirmation)
FTrade (Fill)
4Canceled (Cancel confirmation)
8Rejected
CExpired

5. Typical Message Flows

5.1 Normal Order Flow

Client                                  Broker
  |                                      |
  | ────── NewOrderSingle (35=D) ──────> |
  |        11=ORD001, 55=AAPL,           |
  |        54=1, 38=100, 40=2, 44=185    |
  |                                      |
  | <── ExecutionReport (35=8) ───────── |
  |     150=0 (New), 39=0 (New)          |
  |     Order accepted                   |
  |                                      |
  | <── ExecutionReport (35=8) ───────── |
  |     150=F (Trade), 39=1 (PartFilled) |
  |     31=185.00, 32=50                 |
  |     50 shares filled at $185.00      |
  |                                      |
  | <── ExecutionReport (35=8) ───────── |
  |     150=F (Trade), 39=2 (Filled)     |
  |     31=185.01, 32=50                 |
  |     Remaining 50 shares at $185.01   |
  |                                      |

5.2 Cancel Order Flow

Client                                  Broker
  |                                      |
  | ── OrderCancelRequest (35=F) ──────> |
  |    11=CANCEL001, 41=ORD001           |
  |    (41=Original Order ID)            |
  |                                      |
  | <── ExecutionReport (35=8) ───────── |
  |     150=4 (Canceled), 39=4           |
  |     Cancel successful                |
  |                                      |

Or cancel rejected:

  | <── OrderCancelReject (35=9) ─────── |
  |     102=1 (Unknown order)            |
  |     Cancel failed: Order not found   |
  |                                      |

5.3 Session Establishment

Client                                  Broker
  |                                      |
  | ────────── Logon (35=A) ───────────> |
  |            98=0 (No encryption)      |
  |            108=30 (Heartbeat 30s)    |
  |                                      |
  | <────────── Logon (35=A) ─────────── |
  |             Session established      |
  |                                      |
  | <───────── Heartbeat (35=0) ──────── |
  |             Every 30 seconds         |
  | ────────── Heartbeat (35=0) ───────> |
  |                                      |

6. Python Implementation Example

6.1 Using QuickFIX

import quickfix as fix
import quickfix44 as fix44

class TradingApplication(fix.Application):
    """FIX Trading Application"""

    def __init__(self):
        super().__init__()
        self.session_id = None
        self.order_id = 0

    def onCreate(self, session_id):
        """Session created"""
        self.session_id = session_id
        print(f"Session created: {session_id}")

    def onLogon(self, session_id):
        """Logged in successfully"""
        print(f"Logged on: {session_id}")

    def onLogout(self, session_id):
        """Logged out"""
        print(f"Logged out: {session_id}")

    def toAdmin(self, message, session_id):
        """Callback before sending admin message"""
        pass

    def fromAdmin(self, message, session_id):
        """Admin message received"""
        pass

    def toApp(self, message, session_id):
        """Callback before sending application message"""
        print(f"Sending: {message}")

    def fromApp(self, message, session_id):
        """Application message received"""
        msg_type = fix.MsgType()
        message.getHeader().getField(msg_type)

        if msg_type.getValue() == fix.MsgType_ExecutionReport:
            self._handle_execution_report(message)

    def _handle_execution_report(self, message):
        """Handle execution report"""
        exec_type = fix.ExecType()
        message.getField(exec_type)

        if exec_type.getValue() == fix.ExecType_FILL:
            # Fill report
            order_id = fix.ClOrdID()
            last_px = fix.LastPx()
            last_qty = fix.LastQty()

            message.getField(order_id)
            message.getField(last_px)
            message.getField(last_qty)

            print(f"Fill: {order_id.getValue()} "
                  f"{last_qty.getValue()} @ {last_px.getValue()}")

    def send_new_order(self, symbol: str, side: str,
                       quantity: int, price: float):
        """Send new order"""
        self.order_id += 1
        cl_ord_id = f"ORD{self.order_id:06d}"

        order = fix44.NewOrderSingle()

        # Required fields
        order.setField(fix.ClOrdID(cl_ord_id))
        order.setField(fix.Symbol(symbol))
        order.setField(fix.Side(fix.Side_BUY if side == 'buy'
                                else fix.Side_SELL))
        order.setField(fix.OrderQty(quantity))
        order.setField(fix.OrdType(fix.OrdType_LIMIT))
        order.setField(fix.Price(price))
        order.setField(fix.TimeInForce(fix.TimeInForce_DAY))
        order.setField(fix.TransactTime())

        fix.Session.sendToTarget(order, self.session_id)

        return cl_ord_id

    def cancel_order(self, orig_cl_ord_id: str, symbol: str, side: str):
        """Cancel order"""
        self.order_id += 1
        cl_ord_id = f"CXL{self.order_id:06d}"

        cancel = fix44.OrderCancelRequest()

        cancel.setField(fix.ClOrdID(cl_ord_id))
        cancel.setField(fix.OrigClOrdID(orig_cl_ord_id))
        cancel.setField(fix.Symbol(symbol))
        cancel.setField(fix.Side(fix.Side_BUY if side == 'buy'
                                 else fix.Side_SELL))
        cancel.setField(fix.TransactTime())

        fix.Session.sendToTarget(cancel, self.session_id)

        return cl_ord_id

6.2 Configuration File

# fix_client.cfg

[DEFAULT]
ConnectionType=initiator
ReconnectInterval=5
FileStorePath=./store
FileLogPath=./log
StartTime=00:00:00
EndTime=00:00:00
UseDataDictionary=Y
DataDictionary=./FIX44.xml
ValidateUserDefinedFields=N

[SESSION]
BeginString=FIX.4.4
SenderCompID=YOUR_CLIENT_ID
TargetCompID=BROKER_ID
SocketConnectHost=fix.broker.com
SocketConnectPort=9876
HeartBtInt=30

6.3 Starting the Client

def main():
    settings = fix.SessionSettings("fix_client.cfg")
    application = TradingApplication()
    store_factory = fix.FileStoreFactory(settings)
    log_factory = fix.FileLogFactory(settings)

    initiator = fix.SocketInitiator(
        application, store_factory, settings, log_factory
    )

    initiator.start()

    try:
        # Wait for login
        import time
        time.sleep(2)

        # Send order
        order_id = application.send_new_order(
            symbol="AAPL",
            side="buy",
            quantity=100,
            price=185.00
        )
        print(f"Order submitted: {order_id}")

        # Keep running
        while True:
            time.sleep(1)

    except KeyboardInterrupt:
        initiator.stop()

7. Common Issues and Troubleshooting

7.1 Sequence Number Out of Sync

Problem: "MsgSeqNum too low, expecting 100 but received 50"

Cause: Sequence numbers between client and server are out of sync

Solutions:
1. Send SequenceReset (35=4) to reset sequence number
2. Or set ResetSeqNumFlag (141=Y) in Logon message
3. Production: Use persistent storage to maintain sequence numbers

7.2 Heartbeat Timeout

Problem: Connection dropped, logs show heartbeat timeout

Cause: Network latency or blocking

Solutions:
1. Check network connection
2. Increase HeartBtInt value (but not too much)
3. Ensure application isn't blocking for extended periods

7.3 Order Rejected

Problem: ExecutionReport shows OrdStatus=8 (Rejected)

Troubleshooting steps:
1. Check Tag 58 (Text) for rejection reason
2. Common causes:
   - Insufficient funds
   - Symbol not found
   - Price out of range
   - Invalid quantity

7.4 Message Parsing Tools

def parse_fix_message(raw_message: str) -> dict:
    """Parse FIX message to dictionary"""
    # Replace SOH with visible character
    if '\x01' in raw_message:
        raw_message = raw_message.replace('\x01', '|')

    fields = {}
    for pair in raw_message.split('|'):
        if '=' in pair:
            tag, value = pair.split('=', 1)
            fields[int(tag)] = value

    return fields


def format_fix_message(fields: dict) -> str:
    """Format FIX message for printing"""
    tag_names = {
        8: 'BeginString',
        9: 'BodyLength',
        35: 'MsgType',
        49: 'SenderCompID',
        56: 'TargetCompID',
        34: 'MsgSeqNum',
        52: 'SendingTime',
        11: 'ClOrdID',
        55: 'Symbol',
        54: 'Side',
        38: 'OrderQty',
        40: 'OrdType',
        44: 'Price',
        39: 'OrdStatus',
        150: 'ExecType',
        31: 'LastPx',
        32: 'LastQty',
        10: 'CheckSum',
    }

    lines = []
    for tag, value in sorted(fields.items()):
        name = tag_names.get(tag, f'Tag{tag}')
        lines.append(f"  {tag:>3} ({name}): {value}")

    return '\n'.join(lines)

8. Security Considerations

8.1 Production Requirements

RequirementDescription
TLS/SSLMust use encrypted connections
IP WhitelistBrokers typically restrict connection IPs
Certificate AuthSome brokers require client certificates
Sequence PersistencePrevent sequence conflicts after restart
Message LoggingAll messages must be logged for audit

8.2 Test Environment

1. Most brokers provide UAT (User Acceptance Testing) environment
2. Test all message types in UAT first
3. Simulate various exception scenarios (network disconnect, message out-of-order, etc.)
4. Verify complete order lifecycle

9. Common Misconceptions

Misconception 1: FIX protocol is too complex, only professional institutions need it

For quantitative traders connecting directly to brokers, understanding FIX is necessary. Even when using wrapped APIs, understanding the underlying protocol helps with troubleshooting.

Misconception 2: All brokers' FIX implementations are the same

Although FIX is a standard protocol, different brokers may have:

  • Custom Tags
  • Different required field requirements
  • Specific message flows

Misconception 3: Session layer management can be ignored

Proper handling of the session layer (heartbeat, sequence numbers) is fundamental to stable operation. Ignoring these leads to connection instability and message loss.


10. Summary

Key PointDescription
Core UseOrder submission, fill reports, cancellations
Message StructureHeader + Body + Trailer, key-value format
Key MessagesD (New Order), 8 (Execution Report), F (Cancel)
ImplementationQuickFIX is the most commonly used open-source library
Production RequirementsTLS, sequence persistence, complete logging

Extended Reading

Cite this chapter
Zhang, Wayland (2026). FIX Protocol Introduction. In AI Quantitative Trading: From Zero to One. https://waylandz.com/quant-book-en/FIX-Protocol-Introduction
@incollection{zhang2026quant_FIX_Protocol_Introduction,
  author = {Zhang, Wayland},
  title = {FIX Protocol Introduction},
  booktitle = {AI Quantitative Trading: From Zero to One},
  year = {2026},
  url = {https://waylandz.com/quant-book-en/FIX-Protocol-Introduction}
}