Sequence numbers and recovery
MsgSeqNum too low, expecting X but received Y
The receiver expected sequence number X, received a lower number Y without PossDupFlag set, and treated it as a fatal session error. It sends a Logout carrying this text and disconnects.
What it means
Every FIX message carries MsgSeqNum (34). Each side keeps two counters per session: the next number it will send and the next number it expects to receive. A number lower than expected means the sender has lost track of what it already sent, so the receiver can no longer trust the stream.
A low number with 43=Y is a different case. That is a resend of a message the receiver already has, and it is ignored without an error. Only a low number without PossDupFlag ends the session.
The exact wording comes from QuickFIX and the engines modelled on it. Other engines phrase it differently, but the Logout Text (58) usually names both numbers. X is what the receiver expected; Y is what arrived.
Common causes
- The sending side reset its outgoing sequence to 1 (restart with an empty message store, deleted store files, a fresh deployment) and the other side did not.
- Only one side resets at logon or at the start of the trading day, or the two sides reset at different times.
- Failover to a standby engine whose message store is behind the primary.
- Two processes logged on with the same CompIDs and both sent messages, so their numbers interleaved.
- A manual sequence change made on the wrong side or in the wrong direction.
How to fix it
On the side that sent the low number
- Set your next outgoing sequence number to the value the counterparty expects (X in the text). In QuickFIX this is the sender sequence number in the message store; most engines expose it as an admin command or a sequence file.
- If both sides agree to start fresh, send Logon with 141=Y and MsgSeqNum (34) set to 1. Both sides then reset both counters to 1. Agree this first: anything not yet delivered in either direction is lost.
- Remove the cause: keep the message store on persistent storage, use the same reset schedule as the counterparty, and replicate the store to any standby engine.
On the side that logged out
- Do not lower your expected inbound number to accept Y unless you are sure the messages from Y to X-1 are new to you. Lowering it can make you process the same orders or fills twice.
- Check whether your side reset unexpectedly. If your store was restored from an old copy, the number you expect may be the stale one. Compare both logs before changing anything.
Example
The buy side restarted with an empty store and logs on with 34=1. The sell side expected 2041 and answers with a Logout.
1. Buy side to sell side: Logon with MsgSeqNum 1
8=FIX.4.4|9=70|35=A|49=BUYSIDE|56=SELLSIDE|34=1|52=20261020-07:00:00.000|98=0|108=30|10=075|
2. Sell side to buy side: Logout with the reason
8=FIX.4.4|9=112|35=5|49=SELLSIDE|56=BUYSIDE|34=873|52=20261020-07:00:00.004|58=MsgSeqNum too low, expecting 2041 but received 1|10=244|
SOH (0x01) is shown as |. BodyLength (9) and CheckSum (10) are calculated as if every | were SOH.
Open this example in the parserQuestions
- Can I accept the lower sequence number and carry on?
- Only by lowering the expected inbound number on the receiving side, and only if you know the messages from that number onward have not been processed. Otherwise you risk acting on the same order or execution twice. Raising the sender's outgoing number is the safer fix.
- Does ResetSeqNumFlag (141=Y) fix it?
- Yes, if both sides accept a reset. A Logon with 141=Y and MsgSeqNum 1 tells the counterparty to set both directions back to 1. Messages that were in flight are not recovered, so reconcile open orders afterwards.
- Why does it happen every Monday?
- Usually one side resets its sequence numbers for the new trading week and the other does not. Agree a single reset time with the counterparty and configure both engines with it.