Sequence numbers and recovery
MsgSeqNum too high, expecting X but received Y
The receiver got sequence number Y while it expected X, so messages X to Y-1 are missing. This is not fatal: it sends a ResendRequest (35=2) for the gap and processes the queued messages once the gap is closed.
What it means
A gap means messages were sent but not received, or the sender skipped numbers. The receiver keeps the session up and sends Resend Request (35=2) with BeginSeqNo (7) set to X and EndSeqNo (16) set to 0, which means "everything from X onward". FIX 4.0 and 4.1 used 999999 for the same purpose.
The sender answers by replaying the missing application messages with 43=Y and OrigSendingTime (122), and by sending Sequence Reset (35=4) in GapFill mode (123=Y) over session messages such as heartbeats that are never replayed.
A gap at logon is normal after a disconnect: the Logon arrives with a higher number than expected. The receiver answers the Logon first, then requests the resend. From FIX 4.4, NextExpectedMsgSeqNum (789) on the Logon lets each side state what it expects next.
Common causes
- The receiver was disconnected while the counterparty kept generating messages, which is the normal case after a reconnect.
- The receiver restored an old message store or reset its inbound counter.
- The sender skipped numbers: a bug in a home-grown engine, or someone raised the outgoing number by hand.
- A message was dropped as garbled (wrong BodyLength or CheckSum). It does not count, so the next message looks like a gap.
How to fix it
On the side that must answer the ResendRequest
- Replay the requested range from your store. Each application message keeps its original MsgSeqNum (34), carries 43=Y and OrigSendingTime (122) (the original SendingTime (52)), and gets a new SendingTime.
- Do not replay session messages such as Heartbeat, TestRequest, Logon or ResendRequest. Cover them with a SequenceReset-GapFill whose MsgSeqNum (34) is the first skipped number and whose NewSeqNo (36) is the next number after the run.
- Decide whether old orders should still go out. Many firms gap-fill over stale New Order Single messages instead of replaying them, so an order does not reach the market minutes late.
- If your store has lost the messages, gap-fill the whole range so the session can move on, then reconcile open orders by other means.
On the side that detected the gap
- Send one ResendRequest per gap and wait for it to complete. Sending another for an overlapping range while the first is in progress can start a resend loop.
- Queue messages above the gap and process them in sequence once the gap is filled.
- Treat replayed messages as possible duplicates: check ClOrdID (11) and ExecID (17) before acting on them.
Example
The buy side expected 100 from the sell side but received 105. It asks for a resend; the sell side replays 100 (a fill) and gap-fills 101 to 104 (heartbeats).
1. Sell side: Execution Report with MsgSeqNum 105 (gap detected)
8=FIX.4.4|9=147|35=8|49=SELLSIDE|56=BUYSIDE|34=105|52=20261020-10:15:02.120|37=OID-778812|11=ORD-1002|17=EXEC-0105|150=0|39=0|55=MSFT|54=2|38=500|151=500|14=0|6=0|10=165|
2. Buy side: ResendRequest from 100 to infinity
8=FIX.4.4|9=71|35=2|49=BUYSIDE|56=SELLSIDE|34=311|52=20261020-10:15:02.125|7=100|16=0|10=109|
3. Sell side: message 100 replayed with PossDupFlag
8=FIX.4.4|9=203|35=8|49=SELLSIDE|56=BUYSIDE|34=100|52=20261020-10:15:02.131|43=Y|122=20261020-10:12:40.004|37=OID-778790|11=ORD-1001|17=EXEC-0100|150=F|39=2|55=MSFT|54=1|38=1000|32=1000|31=187.48|151=0|14=1000|6=187.48|10=060|
4. Sell side: SequenceReset-GapFill over 101 to 104
8=FIX.4.4|9=104|35=4|49=SELLSIDE|56=BUYSIDE|34=101|52=20261020-10:15:02.132|43=Y|122=20261020-10:15:02.132|123=Y|36=105|10=018|
After the GapFill the buy side expects 105, which it already holds in its queue, and processes it.
Because the request ran to infinity, the sell side also replays 105 with PossDupFlag=Y. The buy side has already processed that number and ignores the copy.
SOH (0x01) is shown as |. BodyLength (9) and CheckSum (10) are calculated as if every | were SOH.
Open this example in the parserQuestions
- Is "MsgSeqNum too high" an error I need to fix?
- Not on its own. It is the normal trigger for recovery after a disconnect. It needs attention when it repeats every few messages, when the resend never completes, or when the gap is caused by garbled messages.
- What should EndSeqNo (16) be in a ResendRequest?
- From FIX 4.2, 0 means "up to the latest message". FIX 4.0 and 4.1 used 999999. Requesting to infinity avoids a second request if more messages were sent while the first was in flight.
- Why do I get a resend loop?
- Common causes are a ResendRequest sent again before the first one completes, a GapFill whose NewSeqNo (36) is wrong, or replayed messages that are themselves rejected. Paste both sides of the log into the parser and follow the sequence numbers.