FIX Reject Analysis: Reject Reasons and Codes Ranked from a FIX Log
Reject analysis finds every reject in a FIX log and ranks it by its reason code: SessionRejectReason (373) on session Rejects, OrdRejReason (103) on rejected orders, CxlRejReason (102) on cancel rejects and BusinessRejectReason (380) on business rejects. Free-text reasons in Text (58) are grouped by wording, and messages with a wrong CheckSum or BodyLength are counted, so the most common cause of failures is at the top.
Free and in your browser: the log is read and analysed on your computer, nothing is uploaded.
What the analyzer shows
- Session rejects
- Reject (35=3) messages, ranked by SessionRejectReason (373). RefTagID (371) (the tag the other side objected to) is ranked separately, and RefSeqNum (45) identifies the rejected message.
- Order rejects
- Execution Reports with 150=8 or 39=8, ranked by OrdRejReason (103).
- Cancel rejects
- Order Cancel Reject (35=9) messages, for cancel and replace requests alike, ranked by CxlRejReason (102).
- Business rejects
- Business Message Reject (35=j) messages, ranked by BusinessRejectReason (380).
- Missing reason code
- A reject without its reason tag is counted as, for example, "Order reject without 103", so missing codes are visible rather than dropped.
- Reject texts
- Text (58) on every reject, grouped by wording: lower case, words that contain digits replaced by #, quoted parts replaced, spaces collapsed, trailing punctuation removed. "Order exceeds credit limit of 5000000 USD" and the same text with another amount become one group. The 50 largest groups are listed.
- CheckSum and BodyLength errors
- Every message has CheckSum (10) and BodyLength (9) recalculated; messages where the written value is wrong are counted.
- Missing header fields
- Messages without one of the required header tags (8, 9, 35, 49, 56, 34 and 52) or without the CheckSum (10) trailer.
- Links to fixes
- Codes and texts that match a known error link to the troubleshooting page that explains it.
Example from the demo log
These figures are calculated from the demo trading day that the analyzer loads with See it on the demo log, so they match what you see there. In the analyzer every bar and row opens the FIX messages behind it.
Rejects
24
all reject messages
Session rejects
4
35=3, tag 373
Order rejects
14
35=8, tag 103
Cancel rejects
5
35=9, tag 102
Business rejects
1
35=j, tag 380
CheckSum errors
1
BodyLength 0
Reject reasons
373, 103, 102 and 380 codes, ranked
- 103=99 Other5
- 102=0 Too late to cancel4
- 103=1 Unknown symbol2
- 103=3 Order exceeds limit2
- 103=13 Incorrect quantity2
- 373=1 Required tag missing1
- 373=6 Incorrect data format for value1
- 373=10 SendingTime accuracy problem1
- 373=5 Value is incorrect (out of range) for this tag1
- 103=0 Broker or exchange option1
- 103=15 Unknown account(s)1
- 103=2 Exchange closed1
- 102=1 Unknown order1
- 380=3 Unsupported message type1
Reject texts
Text (58) grouped by wording
| Text (58) | Count |
|---|---|
| Too late to cancel | 4 |
| Unknown symbol APPL | 2 |
| Order exceeds credit limit of 5000000 USD | 2 |
| Quantity must be a multiple of 10 | 2 |
| Price outside the static price band | 2 |
| Required tag missing | 1 |
| Short sale not allowed on this account | 1 |
| Price outside the dynamic price band | 1 |
| Unknown order | 1 |
| Unsupported message type AF | 1 |
The demo day has 24 reject messages: 4 session Rejects (35=3), 14 order rejects, 5 cancel rejects and 1 business reject. The most frequent code is 103=99 Other, with 5.
That code means Other and says nothing by itself; the reason is in Text (58), which the analyzer groups and ranks next to the codes. The largest groups here are "Too late to cancel" (4), "Unknown symbol APPL" (2) and "Order exceeds credit limit of 5000000 USD" (2).
"Order exceeds credit limit of 5000000 USD" is grouped as "order exceeds credit limit of # usd", so the same reason with another amount, quantity or ID counts as one group.
Through RefTagID (371), the session Rejects point at Side (54), OrderQty (38), SendingTime (52) and TimeInForce (59). 1 message has a wrong CheckSum (10), placed there on purpose to show the check.
How to read it: what good and bad look like
- Start with the largest group. Rejects tend to have one dominant cause; fixing that one usually removes most of the count.
- Session rejects (35=3) mean the message itself was wrong: a missing tag, a bad value, a bad format or a timing problem. They are fixed in the sender's message building, dictionary or clock, not in trading.
- Order rejects (103) and business rejects (380) mean the message was valid FIX but could not be accepted: unknown symbol, closed market, limits, permissions. They are fixed in reference data, configuration or the order itself.
- Code 99 (Other) on OrdRejReason, or 0 (Other) on BusinessRejectReason, carries no information. The Text (58) groups are where the real reason is, which is why they are ranked next to the codes.
- Cancel rejects with 102=0 (Too late to cancel) are a race with a fill or expiry and are normal in small numbers. Many 102=1 (Unknown order) point to a ClOrdID or OrigClOrdID mismatch.
- Any CheckSum or BodyLength error is worth investigating: the bytes were changed or cut somewhere between the two engines, or the log writer altered them. Real sessions usually reject or drop such messages.
- For checks before sending, the parser's Dictionary tab checks every message against the dictionary of its FIX version and shows which SessionRejectReason a counterparty would send for each problem.
Common causes and fixes
When a figure on this page looks wrong, these pages explain the errors most often behind it and how to fix them.
- Order rejected (103 reasons)
- Business Message Reject (380)
- Required tag missing (373=1)
- Value is incorrect (out of range) (373=5)
- Incorrect data format for value (373=6)
- SendingTime accuracy problem (373=10)
- Too late to cancel (102=0)
- Unknown order (102=1)
- Invalid CheckSum
- Incorrect BodyLength
- Guide: Reading an Execution Report
- Guide: FIX session layer
For engines: POST a log to the API
The same statistics are available over HTTP for scripts, monitoring jobs and engines. POST a log as text, gzip or a multipart file and get JSON, an HTML report or a plain-text summary back. The request is processed in memory and not stored.
curl --data-binary @fix.log -H "Content-Type: text/plain" "https://fixparser.zagtrader.com/api/v1/analyze"
Fields in the JSON response for this page:
stats.rejects.total: reject messagesstats.rejects.session[], .order[], .cancel[], .business[]: tag, code, label, count, slug per reasonstats.rejects.texts[]: key, sample and count per Text (58) groupstats.rejects.refTags[]: RefTagID (371) countsstats.rejects.checksumErrors, .bodyLengthErrors: messages that fail the checksstats.rejects.missingFields: messages missing required header tags
Questions
- What are the FIX reject reason tags?
- SessionRejectReason (373) on a session Reject (35=3), OrdRejReason (103) on an Execution Report that rejects an order, CxlRejReason (102) on an Order Cancel Reject (35=9) and BusinessRejectReason (380) on a Business Message Reject (35=j). Each has its own list of codes.
- What is the difference between a session reject and a business reject?
- A session Reject (35=3) says the message broke the FIX rules: a missing or invalid tag, a wrong format, a bad sequence or time. A Business Message Reject (35=j) says the message was valid FIX but the application could not process it, for example an unsupported message type or an unknown ID.
- How are reject texts grouped?
- Text (58) is lower-cased, every word that contains a digit becomes #, quoted parts and extra spaces are removed, and texts that end up the same count as one group. Messages that differ only in an amount, an ID or a price land in the same group.
- Why is my most common reject code 99 Other?
- Many venues and brokers send OrdRejReason 99 with the real reason in Text (58). Look at the text groups, which the analyzer ranks next to the codes.
- Does the analyzer validate messages against the FIX dictionary?
- The Analytics view recalculates CheckSum (10) and BodyLength (9) and checks the required header tags of every message. The Dictionary tab in the parser goes further: it checks each message against the dictionary of its FIX version and names the SessionRejectReason a counterparty would send.
Reference
More of what the analyzer measures
- Message rate: Messages per second, peak second and busiest minute, mix by MsgType and category, inbound and outbound.
- Order outcomes: How every order ended: filled, partly filled, cancelled, expired, rejected or open, with fill, reject and cancel rates.
- Execution analysis: Execution Reports by ExecType (150), partial and full fills, and the fill-size distribution from LastQty (32).
- Trading activity: Volume and traded value per symbol and currency, side, order type, time in force, venues, order-to-trade and cancel-to-fill.
- Order latency: New order to ack, first fill, cancel and replace times, and Test Request to Heartbeat: p50, p90, p99 and max.
- Session health: Per session: heartbeats, test requests, sequence gaps, resends and gap fills, resets, logons, logouts and the longest silence.
- FIX Log Analyzer: every statistic, how privacy works and how to start