ZagTrader

FIX Order Outcomes: Fill Rate, Reject Rate and Cancel Rate from a FIX Log

An order outcome is the final state of one order in the log. The analyzer chains every message of an order through ClOrdID (11), OrigClOrdID (41) and OrderID (37), reads the last OrdStatus (39) and whether the order had fills, and sorts each order into filled, partly filled then cancelled or expired, cancelled, expired, rejected or still open. From those counts it calculates the fill rate, the reject rate and the cancel rate.

Free and in your browser: the log is read and analysed on your computer, nothing is uploaded.

What the analyzer shows

Orders
Groups of New Order Single (35=D), New Order Multileg (35=AB), Order Cancel/Replace Request (35=G), Multileg Order Cancel/Replace (35=AC), Order Cancel Request (35=F), Order Status Request (35=H), Execution Report (35=8) and Order Cancel Reject (35=9) messages linked by ClOrdID (11), OrigClOrdID (41) and OrderID (37). A replace carries the new ClOrdID in tag 11 and the previous one in tag 41, so the whole chain stays one order. Groups with no new order and no Execution Report, such as a lone cancel request for an order the log does not show, are left out.
Filled
The last OrdStatus (39) is 2 (Filled).
Partly filled then cancelled or expired
The last OrdStatus is 4 (Canceled), C (Expired) or 3 (Done for day) and the order had fills: CumQty (14) above zero or at least one fill Execution Report.
Cancelled
The last OrdStatus is 4 (Canceled) and the order had no fills.
Expired or done for day
The last OrdStatus is C (Expired) or 3 (Done for day) and the order had no fills.
Rejected
The last OrdStatus is 8 (Rejected), or the new order was answered by a session Reject (35=3) whose RefSeqNum (45) is the order's MsgSeqNum (34).
Open, and open and partly filled
Any other last status, such as 0 (New), A (Pending New), 6 (Pending Cancel) or E (Pending Replace): the order was still working when the log ended. With fills (or OrdStatus 1) it is open and partly filled.
No Execution Report
The log has the new order but no Execution Report for it and no session Reject.
Fill rate
Orders with at least one fill (filled, partly filled then cancelled or expired, open and partly filled), divided by all orders.
Reject rate
Rejected orders divided by all orders.
Cancel rate
Orders that ended cancelled, plus orders partly filled then cancelled or expired, divided by all orders.
Order requests
Counts of new orders (35=D, AB and the orders inside a New Order List (35=E)), replaces (35=G, AC), cancels (35=F), mass cancels (35=q) and status requests (35=H).

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.

Orders

2,041

2,843 fills

Fill rate

75.4%

orders with a fill

Reject rate

0.9%

18 rejected

Cancel rate

25.1%

513 orders

Replaces

185

35=G and AC

Cancels

360

35=F

Order outcomes

Final state of 2,041 orders

  • Filled 1,415
  • Partly filled then cancelled or expired 98
  • Open and partly filled 26
  • Open 37
  • Cancelled 415
  • Expired or done for day 32
  • Rejected 18
  • Filled1,415 69.3%
  • Partly filled then cancelled or expired98 4.8%
  • Open and partly filled26 1.3%
  • Open37 1.8%
  • Cancelled415 20.3%
  • Expired or done for day32 1.6%
  • Rejected18 0.9%
Demo log: one generated trading day (2026-10-19) on three FIX sessions (FIX 4.2, FIX 4.4 and FIXT.1.1).

Of 2,041 orders in the demo day, 1,415 (69.3%) were filled completely and 1,539 had at least one fill, a fill rate of 75.4%. 415 were cancelled without a fill and 98 were partly filled before being cancelled or expiring, which makes the cancel rate 25.1%.

18 orders were rejected, a reject rate of 0.9%; the reject analysis shows the reasons. 63 orders were still working when the log ended (37 open, 26 open and partly filled), and 32 expired or were done for the day without a fill.

Behind those outcomes are 2,041 new orders, 185 replace requests and 360 cancel requests, each chained to its order through ClOrdID (11), OrigClOrdID (41) and OrderID (37).

How to read it: what good and bad look like

  • Read the rates together. A low fill rate with a low reject rate usually means passive limit orders that never traded or were cancelled, which is a strategy matter. A low fill rate with a high reject rate is a connectivity, reference data or limit problem.
  • Rejected orders are the first thing to explain. Open the reject analysis to see the OrdRejReason (103) codes and the Text (58) texts behind them.
  • Open orders at the end of the log are normal for good-till-cancel orders or a log cut before the close. A day order still open after the market has closed means a missing Done for day or Expired report.
  • No Execution Report at all for an order points to a message that never reached the other side, a session dropped right after sending, or a log that only holds one direction.
  • A cancel rate that rises during the day while the fill rate falls is a sign that orders are being repriced or pulled more often, for example in a fast market.
  • If an order shows up as two orders, the chain is broken: a replace whose OrigClOrdID (41) does not match the ClOrdID (11) it replaces, or an OrderID (37) that changes. The order lifecycle guide shows the expected chain.

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.

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.orders.orders: orders followed
  • stats.orders.outcomes[]: key, label and count per outcome
  • stats.orders.fillRate: share of orders with a fill (0 to 1)
  • stats.orders.rejectRate: share of orders rejected
  • stats.orders.cancelRate: share of orders cancelled
  • stats.orders.newOrderSingles, .replaces, .cancels: request counts
Read the API documentation

Questions

How is the fill rate of FIX orders calculated?
Each order is followed through ClOrdID (11), OrigClOrdID (41) and OrderID (37). An order counts as filled for the rate when it had at least one fill, whatever its final state. The fill rate is the number of those orders divided by all orders in the log.
How does the analyzer know an order was replaced and not a new order?
A Order Cancel/Replace Request (35=G) carries the new ClOrdID in tag 11 and the ClOrdID it replaces in OrigClOrdID (41). The analyzer joins the two, and also joins messages that share an OrderID (37), so a chain of replaces stays one order.
Which state decides the outcome?
The last OrdStatus (39) seen for the order, together with whether it had any fills. An order whose last status is Canceled but which traded first is counted as partly filled then cancelled, not as cancelled.
Why is an order counted as rejected when it has no Execution Report?
Because the counterparty answered it with a session-level Reject (35=3) whose RefSeqNum (45) points at the order's MsgSeqNum (34). The order never reached the order book, so it is counted as rejected.
Is a cancel rate the same as a cancel-to-fill ratio?
No. The cancel rate is a share of orders: how many ended cancelled. The cancel-to-fill ratio counts cancel requests per fill and is shown on the trading activity page.

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.
  • 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.
  • Reject analysis: SessionRejectReason, OrdRejReason, CxlRejReason and BusinessRejectReason ranked, Text (58) grouped, CheckSum and BodyLength errors.
  • 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