FIX PossDup / PossResend Duplicate-Suppression & Residual-Distortion Slippage Playbook
Why this matters
A resend episode is not dangerous only because messages arrive late.
It is dangerous because the same economic event can now appear in multiple transport shapes:
- an original execution report,
- a
PossDupFlag=Yreplay of that report, - a
PossResend=Yapplication resend under a new sequence number, - or an order-status style summary that restates state without representing a new fill.
If the execution stack collapses those possibilities with a naïve rule like:
- “drop every
PossDup=Ymessage,” - “count every new sequence number as new information,”
- or “dedupe by
ClOrdIDonly,”
then residual state becomes corrupted in subtle ways.
That corruption turns directly into slippage:
- true fills get ignored and the strategy over-trades,
- replayed fills get counted twice and the strategy freezes or unwinds,
- a duplicate-clOrdID recovery path gets mistaken for a fresh reject,
- and replay cleanup gets mislabeled as volatility or venue toxicity.
The operational problem is not merely “duplicate handling.” It is economic identity reconstruction under session uncertainty.
Failure mode in one line
During FIX resend/recovery, the application fails to distinguish transport duplicates from economically new events, so residuals drift away from truth and routing logic pays the difference as slippage.
Protocol facts that matter operationally
1) PossDupFlag (43) means possible retransmission on the same sequence-number identity
FIX defines PossDupFlag=Y as a possible retransmission of a message with that sequence number.
It is not a license to ignore the message blindly.
It means the receiver must decide whether the business event is already reflected locally.
2) PossResend (97) is different from PossDup
FIX distinguishes application-level possible resend from session-level retransmission.
PossResend=Y means the message may contain information previously sent under another sequence number.
So “new sequence number” does not imply “economically new event.”
3) OrigSendingTime (122) exists for a reason
When messages are resent, FIX carries the original send timestamp separately. That means the protocol expects receivers to reason about retransmission lineage rather than treating every arriving packet as new truth.
4) The receiver is responsible for duplicate handling
The FIX message-delivery model explicitly pushes duplicate interpretation to the receiving application. If the app cannot distinguish:
- transport replay,
- application resend,
- status restatement,
- and true incremental execution, then session recovery becomes an execution-quality bug.
5) Duplicate ClOrdID can be valid or invalid depending on resend semantics
FIX order-state matrices explicitly show that a new order with a previously used ClOrdID plus PossResend=Y can require the broker to confirm the current order state, while a non-resend duplicate can be rejected as a duplicate order.
That means duplicate-looking identifiers do not carry one universal meaning.
6) Ordered delivery assumptions break in messy real recovery paths
Engine implementations and session docs warn that during resend handling, messages may be queued, replayed, or even observed twice in different forms depending on configuration. A controller that assumes “first sighting wins” or “arrival order equals business order” will eventually mis-price its own residual.
Observable signatures
1) Residual jumps after resend windows with no corresponding market event
- Parent residual shrinks or grows after replay reconciliation.
- No meaningful quote move or new child-order wave explains it.
- The jump clusters around sequence-gap recovery.
2) Fill totals disagree across hot path vs post-trade reconciliation
- Real-time strategy shows underfill or overfill.
- Later reconciliation from drop copy / clearing / end-of-run ledger says otherwise.
- Differences concentrate in sessions with
PossDuporPossResendtraffic.
3) New sequence numbers produce no real economic change
- Execution reports arrive with fresh
MsgSeqNumvalues. - Yet cum/leaves state or economic identity says the event was already known.
- Naïve “new seq = new event” logic double-counts or double-reacts.
4) PossDup=Y messages sometimes matter and sometimes do not
- Some are true replays already accounted for.
- Some are the only arrival the application actually processed because the original was lost locally.
- Systems that always drop or always accept them generate different but equally bad tail costs.
5) Duplicate-order rejects spike after session instability
ClOrdIDreuse under recovery conditions produces a mix of order-status, reject, and normal acceptance paths.- Strategy logic treats them uniformly and creates cancel/retry churn.
6) TCA shows unexplained cleanup bursts after reconnects
- Overfills, unnecessary hedges, or sudden completion bursts appear after session repair.
- These get blamed on “fast tape” while the real cause is state distortion from duplicate suppression.
Mechanical path to slippage
Step 1) A sequence gap, reconnect, or long-ack ambiguity happens
The session enters resend mode, or the application emits a PossResend=Y order because it is unsure whether the earlier order was processed.
Step 2) The same business event can now be represented more than once
For example:
- original execution report,
- replayed execution report with
PossDup=Y, - order-status restatement,
- duplicate
ClOrdIDflow withPossResend=Y.
Step 3) The application uses the wrong dedupe key
Common bad keys:
MsgSeqNumonly,ClOrdIDonly,PossDupFlagonly,- arrival timestamp only.
Step 4) Economic state diverges from transport state
The app starts to confuse:
- transport uniqueness,
- order identity,
- execution identity,
- and current order summary.
Step 5) Residual control becomes wrong in one of two directions
- under-counted fills → strategy thinks it still has work left and over-trades,
- double-counted fills → strategy believes work is done and misses or unwinds.
Step 6) Router pays the tax
That tax appears as:
- catch-up aggression,
- accidental overfill,
- hedge cleanup,
- queue resets from retry churn,
- or passive opportunity loss from false completion.
Core model
Define:
E_true(t): true set of economically distinct execution/state eventsM_rx(t): received FIX messagesK_tr(m): transport identity of messagem(e.g. seq-num lineage, resend markers)K_ec(m): economic identity extracted from messagem(e.g. exec identity, state transition identity, cumulative state)A(m): application action taken on messagem(apply,ignore,summarize,quarantine)R_true(t): true residual quantityR_obs(t): observed residual quantity in the execution controllerD_miss(t): cost from under-crediting real fillsD_double(t): cost from double-crediting already-accounted fillsQ_churn(t): queue/routing cost from duplicate-order recovery churn
Then:
R_obs(t) = R_true(t) + epsilon_drop_real(t) - epsilon_double_apply(t) + epsilon_state_restate(t)
where:
epsilon_drop_real(t)comes from discarding a message that is transport-duplicate-looking but economically necessary,epsilon_double_apply(t)comes from treating replay or resend as a new fill/state change,epsilon_state_restate(t)comes from confusing summary/status messages with incremental events.
A practical slippage decomposition is:
IS_dup ≈ D_miss + D_double + Q_churn + cleanup_cost + false_completion_cost
Interpretation:
- D_miss: true fills ignored → extra trading,
- D_double: fills counted twice → stop too early or unwind later,
- Q_churn: duplicate order handling causes rejects/retries and queue loss,
- cleanup_cost: hedge/cancel/unwind after reconciliation,
- false_completion_cost: opportunity cost from believing the order is done when it is not.
Identity taxonomy: what must not be conflated
A) Transport identity
Questions like:
- Is this the same sequence-number message being replayed?
- Is it a resent packet form of something already sent?
Useful fields:
MsgSeqNumPossDupFlagPossResendOrigSendingTimeSendingTime
B) Order identity
Questions like:
- Which logical order / chain is this about?
- Is this a duplicate
ClOrdID, a valid resend, or a new intent?
Useful fields:
ClOrdIDOrigClOrdID- venue order ID / broker order ID
C) Execution identity
Questions like:
- Is this a new fill or a replay/restatement/correction of an existing fill?
Useful fields:
ExecIDExecType- cumulative/leaf quantities
- trade date / last shares / last px when applicable
D) State-summary identity
Questions like:
- Is this incremental economic news?
- Or a summary of already-known state such as order-status confirmation?
Useful fields:
ExecType=Order StatusOrdStatusCumQtyLeavesQty- current working parameters
Most bugs happen when one layer is used as a substitute for another.
State ambiguity taxonomy
1) Blind PossDup discard
Every PossDup=Y message is thrown away.
Risk: a message not previously applied locally gets dropped, so real fills remain uncredited.
2) Fresh-sequence optimism
Any new MsgSeqNum is treated as new economic information.
Risk: PossResend=Y or status-style replay creates double application.
3) ClOrdID-only dedupe
Any repeated ClOrdID is treated as duplicate and invalid.
Risk: valid resend semantics are misread; current order state is reconstructed incorrectly.
4) ExecID-only dogmatism without correction/restatement rules
Everything with a repeated ExecID is dropped, regardless of context.
Risk: correction, restatement, or state-summary semantics get lost.
5) CumQty incrementalization bug
A summary message showing current CumQty is misread as “new fill of CumQty.”
Risk: fills are double-added from restated cumulative state.
6) Arrival-order literalism
First arrival is always believed; later replay/status confirmation is ignored.
Risk: local packet loss or partial pipeline failure leaves the system permanently wrong until batch reconciliation.
Feature set worth modeling
Session / recovery features
resend_request_countpossdup_msg_ratepossresend_msg_rateorig_sending_time_gap_msrecovery_window_msgap_queue_depth
Duplicate-interpretation features
possdup_applied_ratepossdup_dropped_ratepossresend_duplicate_order_rateduplicate_clordid_status_confirm_rateduplicate_clordid_reject_ratestate_summary_vs_incremental_conflict_rate
Residual-integrity features
real_time_vs_reconciled_cumqty_gapreal_time_vs_reconciled_leaves_gapdouble_applied_exec_candidate_countdropped_real_exec_candidate_countpost_recovery_residual_jump_qtyresidual_confidence_score
Execution-impact features
catchup_qty_after_reconcilecleanup_qty_after_reconcileretry_reject_burst_factorqueue_reset_after_duplicate_reject_ratefalse_completion_pause_mspost_duplicate_window_markout_1s_5s_30s
Highest-risk situations
1) Manual or automated resend after long unacknowledged order windows
If the client suspects an order was not received and reissues with PossResend=Y, duplicate-order interpretation becomes economically critical.
2) Session recovery during active repricing
If fills, cancels, and replaces are already flowing, duplicate suppression errors compound quickly into residual distortion.
3) Multi-channel truth with hot path + drop copy
One channel may apply an event while another replays or restates it. If the ledger lacks clear economic identity, reconciliation becomes destructive rather than corrective.
4) Strategies that trade from cumulative state deltas
Anything that converts CumQty movement directly into new child urgency is vulnerable to summary-vs-incremental confusion.
5) Tight-deadline execution
A brief false residual near the end of a schedule can force aggressive cleanup flow.
6) Venues/brokers with uneven replay behavior
Different counterparties vary in how they replay application messages, use GapFill, or answer duplicate-order situations. One global dedupe policy is usually too crude.
Regime state machine
CLEAN
- No resend ambiguity.
- Normal event application rules.
TRANSPORT_AMBIGUOUS
Trigger:
- sequence gap, reconnect, or resend window opens.
Actions:
- lower residual-confidence score,
- stop using arrival order as sole truth,
- log transport lineage explicitly.
ECONOMIC_ID_REBUILD
Trigger:
PossDup/PossResend/ duplicate-order patterns detected.
Actions:
- map each incoming message to:
- transport identity,
- order identity,
- execution identity,
- summary-vs-incremental classification.
- quarantine events that cannot yet be classified safely.
SAFE_APPLY
Trigger:
- message classified as economically new.
Actions:
- apply exactly once to residual and state ledger,
- record the economic identity as consumed,
- retain transport lineage for audit.
SAFE_IGNORE
Trigger:
- message classified as transport replay of already-applied economic event.
Actions:
- ignore economically,
- keep observability trail,
- do not mutate residual.
SAFE_SUMMARIZE
Trigger:
- message is current-state confirmation or order-status restatement.
Actions:
- reconcile state ledger if needed,
- do not create synthetic incremental fills,
- use for confidence recovery rather than urgency trigger.
SAFE_CONTAIN
Trigger:
- residual discrepancy exceeds threshold,
- duplicate-order rejects spike,
- or event classification confidence falls.
Actions:
- cap aggression,
- suppress catch-up bursts,
- serialize new risk until authoritative state converges.
Control rules that actually help
1) Keep separate ledgers for transport events and economic events
A message can be new at the transport layer and old at the economic layer. Do not use one ledger to do both jobs.
2) Dedupe fills by economic identity, not just resend markers
PossDup=Y does not automatically mean “ignore,” and lack of PossDup does not automatically mean “apply.”
Use ExecID / execution semantics / cumulative consistency checks.
3) Treat order-status summaries as summaries
If the message conveys current state rather than new execution, reconcile state without manufacturing incremental quantity.
4) Never use ClOrdID alone as the duplicate oracle
Duplicate ClOrdID may mean:
- valid
PossResendrecovery, - genuine erroneous duplicate,
- or state-confirmation path. Interpret it in context.
5) Add a residual-confidence score
When duplicate ambiguity rises, the system should reduce urgency confidence rather than respond with more aggression.
6) Quarantine ambiguous events instead of forcing binary apply/drop
If classification is incomplete, hold the event briefly and reconcile against cumulative state or secondary channels.
7) Attribute duplicate-suppression tax in TCA separately
Otherwise “random reconnect noise” keeps polluting volatility and venue-toxicity buckets.
TCA / KPI layer
Track these explicitly:
EDA — Economic Duplicate Accuracy
Share of replay/resend messages classified correctly against post-hoc truth.DRR — Dropped Real-event Rate
Fraction of economically necessary events incorrectly discarded.DAR — Double-Apply Rate
Fraction of already-applied economic events applied again.RCG — Residual Convergence Gap
|R_obs - R_reconciled| / parent_qtyPSJ — Post-Session-Jump
Residual quantity jump immediately after recovery/reconciliation.DCRT — Duplicate ClOrdID Recovery Tax
Estimated bps lost from duplicate-order reject/retry/status-confirm churn.FCP — False Completion Pause
Time spent under-trading because duplicate logic thought the parent was already done.OCR — Over-Credit Recovery
Cleanup quantity generated after double-counted fills are corrected.
Segment by:
- counterparty,
- venue,
- session phase,
- tactic,
- and whether the ambiguity came from
PossDup,PossResend, or duplicate-order recovery.
Validation approach
Replay / backtest questions
- How often did a
PossDup=Ymessage represent an event not previously applied by the hot path? - How often did a fresh sequence-number message with
PossResend=Ycarry no new economic information? - How often were
CumQty/LeavesQtysummary fields accidentally converted into incremental fills? - How much cleanup flow disappears if ambiguous events are quarantined and reconciled before urgency updates?
- Which counterparties produce the highest duplicate-order recovery tax?
Failure-injection drills
Simulate:
- original execution report lost locally but replayed later with
PossDup=Y, PossResend=Yduplicate new order after long ack ambiguity,- duplicate
ClOrdIDwith valid order-status confirmation path, - out-of-order arrival of summary/status and incremental fill messages,
- and mixed order-entry vs drop-copy disagreement during session repair.
Shadow-mode comparator
Run the live controller against a shadow ledger that classifies messages into:
- economically new,
- economically duplicate,
- summary/restatement,
- ambiguous.
Compare residual drift and post-recovery markouts.
Common anti-patterns
- drop-all-PossDup: throws away real events that were only successfully applied on replay.
- count-all-new-seq: double-applies application resends.
- ClOrdID monoculture: treats all duplicate IDs as the same failure mode.
- CumQty delta confusion: interprets state summary as fresh fill.
- single-ledger design: no distinction between transport receipt and economic application.
- no confidence layer: ambiguous recovery immediately feeds urgency logic.
- no dedicated TCA bucket: slippage from duplicate suppression disappears into generic noise.
Minimal implementation sketch
A robust stack usually needs:
transport ledger
- sequence number
- resend markers
- send-time lineage
- replay window membership
economic event ledger
- canonical execution/state identities
- exactly-once application markers
- summary/restatement classification
duplicate-order interpreter
- understands
PossResendcontext, - duplicate
ClOrdIDscenarios, - and status-confirm vs reject semantics
- understands
residual-confidence model
- discounts urgency when event classification confidence falls
quarantine + reconcile path
- short holding lane for ambiguous events,
- resolved using cumulative state and secondary channels
TCA hooks
- measure dropped-real vs double-applied costs separately
Bottom line
Duplicate handling in FIX is not a parser detail.
PossDup, PossResend, OrigSendingTime, duplicate ClOrdID, and order-status confirmations together create a regime where transport novelty and economic novelty diverge. If the execution stack cannot tell them apart, it quietly corrupts residuals and then trades off that corrupted view.
That cost shows up as overfills, missed fills, retry churn, false completion, and cleanup aggression.
The fix is not “be stricter about duplicates.”
The fix is to separate transport identity from economic identity, classify summary vs incremental state correctly, and let residual confidence fall when the lineage is ambiguous.
References
- FIXimate — Tag 43
PossDupFlag: https://fiximate.fixtrading.org/legacy/en/FIX.4.4/tag43.html - FIXimate — Tag 97
PossResend: https://fiximate.fixtrading.org/legacy/en/FIX.4.2/tag97.html - FIXimate — Tag 122
OrigSendingTime: https://fiximate.fixtrading.org/legacy/en/FIX.4.2/tag122.html - OnixS — FIX Message Delivery (ordered processing, possible duplicates/resends): https://www.onixs.biz/fix-dictionary/fixt1.1/section_fix_message_delivery.html
- OnixS — Appendix D F.1.b, Poss resend and duplicate
ClOrdID: https://www.onixs.biz/fix-dictionary/4.4/app_df.1.b.html - B2BITS — Standard Message Header (
PossDupFlagvsPossResendin resend semantics): https://www.b2bits.com/fixopaedia/fixdict11/block_Standard_Message_Header_.html - OnixS — Execution Report <8> semantics: https://www.onixs.biz/fix-dictionary/4.4/msgtype_8_8.html
- OnixS .NET FIX Engine — Resending messages / queueing behavior: https://ref.onixs.biz/net-fix-engine-guide/resending-messages.html