FIX (Financial Information eXchange) protocol was designed in 1992 and looks like it. Tag-value pairs delimited by the ASCII SOH character (0x01). Fields in a prescribed order that parsers nonetheless receive out of order. A specification document larger than most novels. And yet: FIX is the universal language of electronic trading. Every exchange, every prime broker, every ECN speaks it. You will encounter it.
The Wire Format
A FIX message is a sequence of tag=value pairs separated by SOH (displayed here as |):
8=FIX.4.4|9=178|35=D|49=CLIENT1|56=BROKER1|34=1|52=20120807-09:31:02.123|
11=ORD001|55=EUR/USD|54=1|38=1000000|40=2|44=1.08450|59=0|10=123|
Breaking this down:
| Tag | Field name | Value | Meaning |
|---|---|---|---|
| 8 | BeginString | FIX.4.4 | Protocol version |
| 9 | BodyLength | 178 | Byte count of body (validated) |
| 35 | MsgType | D | New Order Single |
| 49 | SenderCompID | CLIENT1 | Sending firm identifier |
| 56 | TargetCompID | BROKER1 | Receiving firm identifier |
| 34 | MsgSeqNum | 1 | Sequence number (must be gapless) |
| 52 | SendingTime | 2012… | UTC timestamp |
| 11 | ClsOrdID | ORD001 | Client-assigned order ID |
| 55 | Symbol | EUR/USD | Instrument |
| 54 | Side | 1 | 1=Buy, 2=Sell |
| 38 | OrderQty | 1000000 | Quantity |
| 40 | OrdType | 2 | 2=Limit |
| 44 | Price | 1.08450 | Limit price |
| 59 | TimeInForce | 0 | 0=Day |
| 10 | CheckSum | 123 | Sum of all bytes mod 256 |
The 9 (BodyLength) and 10 (CheckSum) fields are how FIX validates message integrity — not for security, but for detecting transmission corruption. Tag 8 must be first; tag 10 must be last. Everything else is technically flexible but in practice ordered by convention.
Message Types You’ll Live With
| MsgType | Name | Direction | Purpose |
|---|---|---|---|
| D | New Order Single | Client → Broker | Submit a new order |
| F | Order Cancel Request | Client → Broker | Cancel an existing order |
| G | Order Cancel/Replace | Client → Broker | Modify an existing order |
| 8 | Execution Report | Broker → Client | Fill, partial fill, rejection |
| 9 | Order Cancel Reject | Broker → Client | Cancel request rejected |
| V | Market Data Request | Client → Broker | Subscribe to prices |
| W | Market Data Snapshot | Broker → Client | Full order book / quote |
| X | Market Data Inc. Refresh | Broker → Client | Incremental price update |
| 0 | Heartbeat | Both | Session keepalive (30s default) |
| A | Logon | Both | Session establishment |
| 5 | Logout | Both | Clean session termination |
For an FX broker, the most important ones are D/F/G (order lifecycle on the client side) and 8 (execution reports, which carry fills and rejections).
The Session Layer: Where Things Go Wrong
FIX has a session layer (sequence numbers, heartbeats, logon/logout) sitting below the application layer (orders, quotes). The session layer is the source of most operational pain.
Sequence numbers are monotonically increasing per session, per direction. If you receive message 7 and the next one is 9, you have a sequence gap and must send a ResendRequest. The counterparty should replay messages 8–9. If they can’t (the message was never sent), they send a GapFill. If your application doesn’t handle this correctly, you drop messages silently — which in a trading context means missing fills or cancellations.
Normal flow: Gap scenario:
→ msg seq 5 → msg seq 5
→ msg seq 6 → msg seq 6
→ msg seq 7 → msg seq 8 ← gap! expected 7
→ msg seq 8
← ResendRequest (7,7)
→ msg seq 7 (resent)
→ msg seq 9 (continue)
Session reset happens at start of day (configurable) and invalidates the sequence counter. Botching the reset — starting a new session before the counterparty is ready, or not persisting sequence numbers across process restarts — causes immediate Logon rejection. FIX sessions that haven’t been properly reset are one of the most common morning war stories at trading firms.
Parsing Considerations
Tag-value parsing sounds trivial. At 200,000 messages/second with a 200-byte average message, the naive implementation allocates a HashMap<Integer, String> per message — 200,000 objects plus their key/value strings. Minor GC fires constantly.
The production approach: a pre-allocated parser that writes into a fixed array indexed by tag number (most FIX tags are under 1000). No allocation, O(1) field lookup.
| |
The further optimisation: don’t convert tag values to String at all. Keep them as byte[] slices and only convert to the target type (int, double, String) when accessed. A fill price parsed as a double from bytes is faster than Double.parseDouble(string).
QuickFIX/J vs Rolling Your Own
QuickFIX/J is the standard Java FIX library. It handles session management, sequence numbers, message store and replay, and generates strongly-typed message classes from the FIX specification XML. For getting connected quickly, it’s the right starting point.
Its limitations for latency-sensitive work:
- The generated message classes allocate heavily
- Session management is in separate threads with locking
- The message store (for replay) is file or database backed, adding I/O to the critical path
We ran QuickFIX/J for our low-volume, low-latency-requirement connections (voice broker give-up, operations systems) and a bespoke zero-allocation parser for the high-volume market data feeds and order routing. The bespoke parser was ~4x faster at peak throughput but required maintaining compatibility with each counterparty’s dialect of FIX, which is more work than it sounds — FIX implementations have vendor-specific quirks that the spec doesn’t cover.
The FIX Dialect Problem
The specification exists. Counterparty implementations do not read it faithfully. Common divergences:
- Extra required fields: some brokers require custom tags (6000+) that are not in the spec
- Different enum values:
OrdType=Pfor “Pegged” is not in FIX 4.4 but some venues use it - Timestamp precision: the spec says milliseconds; some counterparties send microseconds or nothing
- Encoding: ASCII by default, but some newer implementations send binary FIX (FAST, SBE) on the same socket
In practice, each counterparty gets its own session configuration and sometimes its own message parser. FIX interoperability is a spectrum, not a guarantee.