# FIX Log Desk > Paste a FIX protocol session capture and find out what actually happened. A real FIX > reader runs free in the browser; four AI lanes then run over what it measured. URL: https://fixlog-desk.skillsafe.ai/ API tutorial: https://fixlog-desk.skillsafe.ai/api.html Token panel: https://fixlog-desk.skillsafe.ai/tokens.html (noindex, not in the sitemap) ## What the work object is A FIX protocol log: `tag=value` fields terminated by SOH, as written by an order management system, an execution management system, a drop-copy session or a venue gateway. Pipe-delimited, `^A`-delimited and semicolon-delimited renderings are all read, as are engine log prefixes carrying a timestamp and an IN/OUT marker, and captures where several messages share one line or where the whole file is one unbroken SOH-delimited stream. ## What the browser does, free, offline, with no account This is not a text search with some regular expressions. It is a reader. - **Delimiter detection per message.** `BeginString(8)`'s value is drawn from `[A-Za-z0-9.]` only, so the first character that is not one of those is the delimiter this message was written with. That is a fact about the format, not a guess about the file — counting pipes would be fooled by a pipe inside a `Text(58)` field. - **Normalisation to SOH before any arithmetic.** `CheckSum(10)` is defined over the SOH form, and a pipe (0x7C) and a SOH (0x01) are not the same byte. A pipe-rendered log therefore has to be normalised before it can be checksummed, and it is. - **Length-prefixed data fields read by byte count.** `RawData(96)`, `Signature(89)`, `XmlData(213)` and the `Encoded*` fields are length-prefixed precisely so they may contain arbitrary bytes including the delimiter. The reader takes exactly the declared number of UTF-8 bytes rather than splitting. - **`BodyLength(9)` and `CheckSum(10)` recomputed from the bytes, never trusted.** BodyLength counts the bytes between the delimiter that ends field 9 and the delimiter that begins field 10; CheckSum is the byte sum modulo 256 as three digits. - **Header order and structure checked.** 8, 9, 35 first in that order, 10 last; empty values; repeating-group counts against the openings that follow them; unknown tags and non-standard MsgTypes reported rather than ignored. - **Sequence streams tracked per direction.** The ordered pair sender→target, not a normalised set — a session has two independent counters and merging them produces an unbroken-looking stream out of two broken ones. Gaps, duplicates without `PossDupFlag`, administrative `SequenceReset` against gap-fills, and `SendingTime(52)` regressions. - **Resend accounting that does not flatter itself.** A SequenceReset with GapFillFlag=Y does not deliver anything - it declares a range administrative - so it is not counted as a resent message, and a request answered only by one is reported as having delivered nothing. The request's SCOPE is also checked against the gap it was sent to repair: a range narrower than the hole can never recover the rest, and that defect is on the requesting side and nothing else surfaces it. - **Session health.** Logon terms including whether the file carries credentials, heartbeat compliance measured the way the specification defines it (the gap between consecutive messages in one direction against the agreed `HeartBtInt`, not heartbeats alone) and reported as a COUNT with every breach listed, because one long silence and a heartbeat timer that is not running are different problems; whether each `TestRequest` was answered by a `Heartbeat` with the matching `TestReqID`; and whether a `SequenceReset` declared an already-delivered message administrative, which is how two sides end up with different views of a session. - **Orders chained, not listed.** `ClOrdID(11)` and `OrigClOrdID(41)` are followed to a root, so an order amended three times is one order with four ids rather than four half-finished orders. `OrderID(37)` is recorded as corroboration and is deliberately not the chain key, because venues differ on whether it survives a replace. - **The FIX order state diagram, walked per chain leg.** Per leg, not per chain: the old ClOrdID legitimately ends at `Replaced` while the new one carries on, and one machine over the merged chain reports `Replaced → Filled` as illegal on every correctly-behaved amendment. - **The arithmetic every venue is supposed to satisfy.** `CumQty(14) + LeavesQty(151) = OrderQty(38)` on non-final states; `LeavesQty` zero on a final state; `CumQty` never decreasing; the `LastQty(32)` values summing to the final `CumQty`; `AvgPx(6)` against the quantity-weighted mean of the fills; no overfill; and no limit order filled outside its own limit price. - **Cancel and replace races**, measured from the request to its RESOLUTION (`Canceled`, `Replaced` or a `CancelReject`) rather than to the pending acknowledgement — an order that is pending cancel is still live on the book, which is the entire reason the pending states exist. - **Execution facts**: fill rate, fill count, venue and liquidity-indicator mix, acknowledgement and first-fill latency, notional, and VWAP **per symbol only**. - **Tape signatures**: order-to-trade ratio, cancel-to-order ratio, time on the book before cancellation, one-sided clusters cancelled unfilled, opposite-side fill pairs on one stated account inside a window. ## Two rules that shape what this app will and will not say **A corrupted message is evidence, not data.** Any message whose CheckSum or BodyLength fails verification is quarantined: shown, counted in the session analysis because it still occupied a sequence number, and excluded from every order, execution and tape number. A receiving engine drops it unread before touching a business field, and so does this. On a capture with one character missing from a price, letting the corrupted value through produced an average fill price of 293 on a stock trading at 402, and every downstream number was wrong in a way that looked like a venue problem. **No market data means no benchmark.** Transaction cost analysis compares a fill against the market at the moment of the fill. This app has no network. So it looks for quotes inside the file — `MarketDataSnapshotFullRefresh(W)` and `MarketDataIncrementalRefresh(X)` entries — and where they exist it measures each fill against the last bid/offer mid before it and says the benchmark is this file's own data and not a consolidated NBBO. Where they do not exist it reports no slippage, no spread capture and no implementation shortfall. A limit price is the trader's instruction, not the market. **And one refusal.** Nothing in this app concludes that anyone manipulated a market. Layering, spoofing, wash trading and marking the close are legal conclusions requiring intent, account relationships and context that a message log does not contain. The app computes the signature statistics a firm's own calibrated alert logic consumes, names the screen that consumes each one, gives at least one ordinary trading explanation for every candidate shape, and offers only `no-action`, `monitor` and `refer-to-compliance` as dispositions. ## Secrets `Password(554)`, `NewPassword(925)`, `RawData(96)`, `SecureData(91)` and `Signature(89)` are replaced with `[REDACTED]` before the facts or the excerpt are built, so they never leave the browser. A redacted copy of the log is downloadable for attaching to a ticket. Messages that were not redacted keep their original framing, so a checksum that failed before still fails afterwards — reframing them would silently repair the corruption that is the evidence. `Account(1)` and the CompIDs are not redacted; they are the identifiers the analysis is about, and the app says so rather than claiming a completeness it does not have. ## The four lanes Each takes the same file. The `task` field in the run input selects one. | task | question | source skill | | --- | --- | --- | | `session` | Did the transport work? | @joellewis/exchange-connectivity | | `lifecycle` | What happened to each order? | @joellewis/order-lifecycle | | `execution` | What can honestly be said about these fills? | @joellewis/trade-execution | | `surveillance` | What would a screen make of this tape? | @joellewis/post-trade-compliance | Verdict words differ per lane: `healthy`/`degraded`/`broken`, `reconciled`/`discrepancies`/`unreconstructable`, `documented`/`gaps`/`not-assessable`, `no-signatures`/`inputs-present`/`escalate-to-review`. ## How the review is kept honest The browser measures first and its numbers go into the run. The review must return one reconciliation entry per critical and high flag by id, so a defect it ignored is displayed as unaccounted for rather than quietly dropped. Every reference it makes is checked against the file, and one that does not exist is shown struck through rather than trusted. The grammar is closed: `M` a message (every integer from 1 to the message count is valid), `O:` an order by any id in its chain, `X` a flag, `S: -> ` one direction of a session, `SEQ: -> #` one sequence number **present or missing**, `SYM:` an instrument, `V:` a market, `T` a FIX tag. `SEQ:` exists because a message that never arrived has no `M`, and in a gap analysis the hole is the subject. ## Free exports, no account Markdown read-out · messages CSV · orders CSV · fills CSV · flags CSV · the whole measurement as JSON · a redacted copy of the log. ## Sources and credit A derived work built on and crediting four skills by @joellewis: `order-lifecycle`, `exchange-connectivity`, `trade-execution` and `post-trade-compliance`. The FIX reader, the sequence analysis, the order reconstruction, the arithmetic checks and every measurement on the page were written for this app. Not affiliated with FIX Protocol Ltd, FINRA, the SEC or any trading venue. FIX is a public protocol specification and this app implements a reader for it. Nothing here is legal, compliance or investment advice. ## Cost The browser side is free and needs no account. Running a lane is metered: a worst-case amount is reserved before the run and only what the run uses is charged. Model `gpt-terra` at 1000 bps publisher markup.