When to Use
Use this skill when a system speaks tag=value FIX to or about CME Group: a CME STP FIX 4.4 trade-capture session, a replay or simulator over archived iLink 2 flow, a conformance harness, or a port of legacy CME FIX order-entry code onto iLink 3.
It covers the four things hand-rolled CME FIX code gets wrong:
- Emitting a "FIX message" with no
BeginString,BodyLength,SendingTimeorCheckSum— a string no counterparty can frame, let alone accept. - Re-requesting a resend on every out-of-sequence message, turning one gap into a resend storm and a disconnect.
- Putting a value in Tag 8000 that CME does not define (only
OandNexist). - Rounding a price to a fixed number of decimals, which mis-prices any instrument quoted more finely.
When NOT to Use
- For live CME order entry. CME order entry no longer accepts tag=value FIX. iLink 2
— the FIX 4.2-based order-entry protocol these tags come from — was decommissioned on
the Market Segment Gateway on 2021-03-28 and on the Convenience Gateway on
2025-04-06. Live order entry is iLink 3: FIX Simple Binary Encoding over the
FIXP session layer. Nothing this module emits will reach CME Globex. Use
to_ilink3_order_fields()for the field mapping and seecme-globex-futures-api-integration. - As a FIX session engine. This module frames messages and tracks sequence numbers.
It does not own a socket, heartbeats,
TestRequestliveness, logon negotiation, resend replay from a message store, or sequence persistence across restarts. For a production session, use a maintained engine (QuickFIX, OnixS) and keep this module for the CME-specific field semantics. Seefix-protocol-session-management-across-venues. - For iLink 3 sequencing. FIXP has no
MsgSeqNum(34), noResendRequest(35=2)and noSequenceReset(35=4). Its recovery isRetransmit Request/NotApplied, and the state machine here does not transfer. Seematching-engine-throttle-and-message-gapping-detection. - For CME market data. MDP 3.0 is SBE multicast, not FIX. See
exchange-multicast-feed-handling.
Prerequisites
- A session identity for the venue in question:
SenderCompID/TargetCompID, plus an operator ID registered under CME Rule 576 (Tag 50 in tag=value FIX; Tag 5392SenderID, 20 bytes, in iLink 3). - Self-Match Prevention IDs registered in the CME Firm Administrator Dashboard (FADB).
An SMP ID must be numeric to survive a port to iLink 3, where Tag 2362 is a
uInt64— a string that is legal in tag=value FIX is not representable there. - Python 3.10+. Standard library only.
Workflow
-
Decide which protocol you are actually on before writing a byte. If the destination is CME order entry, stop: that is iLink 3 SBE, and a tag=value encoder is the wrong tool. If it is CME STP, it is FIX 4.4 — set
begin_string="FIX.4.4". -
Build messages through
build_fix_message()and send them verbatim. It emits8=…|9=…|35=…first and10=…last, with SOH delimiters and a UTCSendingTime(52)at millisecond precision.BodyLengthandCheckSumare computed over exactly those bytes: re-serialising the message downstream invalidates both. -
Populate the CME order tags, with the values CME defines.
1028=Nfor automated flow,Yfor a human-entered order — required on CME order messages since June 2011.50carries the Rule 576 operator ID.7928is the registered SMP ID, and8000is eitherO(cancel oldest — the resting order) orN(cancel newest — the aggressor). Omitting Tag 8000 is legal; CME's default is to cancel the resting order.create_new_order_single()rejects any other value rather than serialising it. -
Pass prices as decimal strings or
Decimal. They are serialised without rounding and without scientific notation. A fixed-precision format is the failure mode here: four decimals turns a 1.05125 FX future into 1.0513, a full tick away. -
Drive inbound sequencing through
process_inbound_message()and act on what it returns.- A
ResendRequest(35=2)means a newly detected gap — send it. Messages that arrive while that request is outstanding are the peer working through the range; the engine deliberately does not re-request on each one. - A
Logout(35=5)means the peer sent a sequence number below the expected one withoutPossDupFlag(43)=Y. The FIX session layer treats that as unrecoverable: send the Logout, then drop the transport connection. Do not keep trading on it. Nonemeans in sequence, an admissible duplicate, or recovery already in flight — three different things. Gate business processing onlast_inbound_accepted, which is true only for an in-sequence message. Applying a duplicate or an out-of-sequenceExecutionReportreplays or reorders fills.
- A
-
Persist both sequence counters. They live in memory here. A process restart that resets them to 1 mid-session desynchronises the session; the reset policy (and when
ResetSeqNumFlag(141)=Yis appropriate) is venue-specific — take it from the venue's session spec, not from this module's defaults. -
When porting to iLink 3, run
to_ilink3_order_fields()first. It fails loudly on the two fields that do not survive a naive port: a non-numeric SMP ID (Tag 2362 is auInt64) and an operator ID longer than the 20-byteSenderIDfield.
Full procedure: see
references/workflows.md. Protocol status, tags, values and sources: seereferences/standards.md. Printable pre-flight checklist: seeassets/checklist.md.
Common Pitfalls
- Treating a delimited tag list as a FIX message. Without
BeginString,BodyLengthandCheckSumthere is nothing for the peer to frame on, and a|-delimited string is a log rendering, not the wire format. The wire delimiter is SOH (\x01). - Re-requesting a resend on every out-of-sequence message. During recovery the peer
is replaying the range you asked for, and those messages are legitimately ahead of your
expectation. Issuing a fresh
ResendRequestfor each one floods the session and ends in a disconnect — the opposite of recovery. - Ignoring a too-low sequence number. Logging it and moving on silently discards
everything the peer sends afterwards, including
ExecutionReports: the position on the book stops matching the position in the process. The session layer requires a Logout and a disconnect, except forSequenceResetwithGapFillFlag(123)=N, which is honoured whatever its own sequence number. - Inventing Tag 8000 values. CME defines exactly
O(CancelOldest) andN(CancelNewest).R,B,CANCEL_RESTINGand similar inventions are rejected by the venue — andOmeans "cancel the oldest, i.e. resting, order", not "cancel outgoing". - Assuming a missing Tag 1028 produces a session-level reject. CME answers an invalid
or missing
ManualOrderIndicatorwith a Business Reject (35=j,BusinessRejectReason(380)=100), not35=3. Code that only inspects35=3will treat the order as live. - Rounding prices to a fixed precision.
f"{price:.4f}"is silently wrong for every instrument quoted below four decimals, and a float price carries binary representation artifacts into the wire format. - Reusing an iLink 2 SMP ID on iLink 3. Tag 7928 was an alphanumeric string; Tag 2362
is a
uInt64.SMP_888has no iLink 3 representation, and the operator ID moves from Tag 50 to Tag 5392 at the same time. - Defaulting the account. A hard-coded placeholder account routes real risk to whatever that string happens to resolve to. Tag 1 is required here for that reason.
- Consuming a sequence number for a message that is never sent. The counter advances when the message is built, so messages must be transmitted in build order and a build that is discarded leaves a permanent hole the peer will ask you to fill.
Verification
- Confirm the message starts
8=FIX.4.x<SOH>9=<n><SOH>35=…and ends10=<nnn><SOH>, and that9=equals the byte count between the end of the9=field and the start of10=, derived independently rather than from the module's own arithmetic. - Confirm the checksum equals the sum of every preceding byte modulo 256, zero-padded to three digits.
- Confirm a price of
1.05125serialises unrounded,Decimal("0.000005")does not become5E-6, andNaN/Infinityraise. - Confirm
smp_instruction="R"raises,"O"and"N"are accepted, andNoneomits Tag 8000 while leaving Tag 7928 in place. - Confirm a value containing SOH or
=is rejected rather than forging a field. - Confirm one gap yields exactly one
ResendRequest, that sequences 6, 7 and 8 arriving during recovery yield none, and that recovery clears once the range is filled. - Confirm a too-low sequence number without
PossDupFlagreturns aLogout(35=5)and marks the session terminated, while the same message with43=Yis discarded quietly. - Confirm
last_inbound_acceptedis true only for the in-sequence message — not for the duplicate and not for the message that arrived mid-recovery, both of which also returnNone. - Confirm
to_ilink3_order_fields()maps50→5392,7928→2362(as an integer) and1028to0/1, and raises on a non-numeric SMP ID. - Run
python -m unittest discover -s skills/cme-stp-fix-and-ilink2-tag-value-encoding/scriptsand confirm a 100% pass rate. - Against a venue test session only: send one built message unmodified through the real transport and confirm it is accepted. A framing bug that unit tests cannot see is one where the transport rewrites the bytes.