Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
587778d
docs: sync resume anchors and PMU claims to v0.2.0 (Codex #127/#128 f…
div0rce Jun 21, 2026
0c3b401
perf: add flamegraph generator and make target (#32)
div0rce Jun 21, 2026
beec2d0
perf: add generated flamegraph artifact on bare-metal Fedora Asahi (#32)
div0rce Jun 21, 2026
59f0fc3
feat: add FIX-like text protocol adapter (#29)
div0rce Jun 21, 2026
872600a
perf: harden flamegraph collapsed-stack parsing (Codex review)
div0rce Jun 21, 2026
0201d54
perf: regenerate flamegraph artifact after parser hardening
div0rce Jun 22, 2026
3e4c8e3
fix: enforce FIX-required ClOrdID on OrderCancelRequest (Codex review)
div0rce Jun 22, 2026
52de5b8
refactor: improve flamegraph.py code health (CodeScene gate)
div0rce Jun 22, 2026
d4be2da
perf: regenerate flamegraph artifact after code-health refactor
div0rce Jun 22, 2026
9c68039
refactor: reduce decode_new_order complexity in fix.cpp (CodeScene gate)
div0rce Jun 22, 2026
4aec1d0
refactor: flatten flamegraph.py remaining complexity (CodeScene)
div0rce Jun 22, 2026
3905059
perf: regenerate flamegraph artifact after complexity flattening
div0rce Jun 22, 2026
68fe197
refactor: remove decoder duplication and split parse_envelope (CodeSc…
div0rce Jun 22, 2026
cb8f99c
refactor: table-driven enum maps + simpler msg-type check (CodeScene)
div0rce Jun 22, 2026
dfa4da2
docs: record resume-anchor sync in PROGRESS current-state (Codex #129)
div0rce Jun 22, 2026
6ef5015
Merge branch 'docs/codex-resume-anchor-sync' into perf/flamegraph-art…
div0rce Jun 22, 2026
31070b1
perf: harden flamegraph.sh classification + sample gating (Codex #130)
div0rce Jun 22, 2026
06b7675
perf: regenerate flamegraph artifact after classification hardening
div0rce Jun 22, 2026
ee5ea4e
Merge branch 'perf/flamegraph-artifact' into feat/fix-text-protocol-a…
div0rce Jun 22, 2026
f213ee7
fix: enforce FIX envelope MsgType position + reject duplicate tags (C…
div0rce Jun 22, 2026
2abb9ca
docs: drop delivered #29 from open-backlog anchors (Codex #131)
div0rce Jun 22, 2026
4a2aa67
docs: scope partial-PMU claim to perf-stat; perf-record is a software…
div0rce Jun 22, 2026
2199820
Merge branch 'docs/codex-resume-anchor-sync' into perf/flamegraph-art…
div0rce Jun 22, 2026
6e8a302
Merge branch 'perf/flamegraph-artifact' into feat/fix-text-protocol-a…
div0rce Jun 22, 2026
5093beb
docs: embed the flamegraph as a visible image in the README
div0rce Jun 22, 2026
1599e5d
Merge branch 'perf/flamegraph-artifact' into feat/fix-text-protocol-a…
div0rce Jun 22, 2026
4fc2f35
Merge remote-tracking branch 'origin/main' into feat/fix-text-protoco…
div0rce Jun 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ include(cmake/ProjectOptions.cmake)
include(cmake/CompilerWarnings.cmake)
include(cmake/Sanitizers.cmake)

add_library(qsl_core src/core/types.cpp src/protocol/codec.cpp src/engine/order_book.cpp
add_library(qsl_core src/core/types.cpp src/protocol/codec.cpp src/protocol/fix.cpp
src/engine/order_book.cpp
src/engine/matching_engine.cpp src/engine/risk.cpp
src/gateway/order_gateway.cpp src/feed/market_data.cpp
src/feed/publisher.cpp src/replay/event_log.cpp src/replay/command.cpp
Expand Down
10 changes: 6 additions & 4 deletions HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,15 +82,16 @@ Current state:
provides real cycles/instructions/branches/branch-misses but no cache-reference/cache-miss support
- issues #95, #28, and #26 were closed by PR #112
- open review request issue: #94
- legacy backlog still open: #29 and #32
- legacy backlog still open: #32 (#29 delivered in this PR, `feat/fix-text-protocol-adapter`)

### Next milestone

There is no active milestone. M0–M49, the Linux artifact refresh (PR #125), and the v0.2.0 release
(PR #127) are merged. The highest-value remaining work is non-code and externally gated: issue #94
(independent external review — needs a human reviewer) and issue #90 (full cache-counter PMU
evidence — needs a PMU microarchitecture that exposes cache events). Low-signal backlog: #32
(flamegraph) and #29 (FIX adapter). Do not invent a new milestone without an explicit human request.
evidence — needs a PMU microarchitecture that exposes cache events). #29 (FIX-like text protocol
adapter) is delivered in this PR; low-signal backlog: #32 (flamegraph). Do not invent a new
milestone without an explicit human request.

### Phase III / IV purpose

Expand All @@ -107,7 +108,8 @@ Current priority order (post-v0.2.0):
2. Issue #90 — full cache-counter PMU evidence. The bare-metal Apple host gives real
cycles/instructions/branches/branch-misses but no cache-reference/cache-miss counters, so this
needs a PMU microarchitecture that exposes cache events (x86_64, or an ARM server core).
3. Low-signal backlog only after the above: #32 (flamegraph), #29 (FIX adapter).
3. Low-signal backlog only after the above: #32 (flamegraph). #29 (FIX adapter) is delivered in
this PR (`feat/fix-text-protocol-adapter`).

### Forbidden shortcuts

Expand Down
15 changes: 10 additions & 5 deletions MILESTONES.md
Original file line number Diff line number Diff line change
Expand Up @@ -469,10 +469,11 @@ Sequential, dependency-ordered. **Build them in order.** Each milestone is one f
> perf/flamegraph). M25 (memory-ordering evidence), M30 (socket profiling/hardening), and M31
> (external review) are new milestones with no prior issue. PR #112 closed
> the remaining tractable systems items **#26** (portable TCP serving beyond one-connection-at-a-time
> accept) and **#28** (realistic synthetic order-flow model). The genuinely **deferred** product/API
> items remain **#29** (FIX adapter), **#30** (web dashboard), **#31** (Docker packaging), and
> **#33** (Pages site) — do not start them before the Phase III/IV systems roadmap unless the human
> explicitly reprioritizes.
> accept) and **#28** (realistic synthetic order-flow model). The human later reprioritized two
> backlog items, now **done**: **#32** (perf/flamegraph) and **#29** (FIX-like text protocol
> adapter). The genuinely **deferred** product/API items remain **#30** (web dashboard), **#31**
> (Docker packaging), and **#33** (Pages site) — do not start them before the Phase III/IV systems
> roadmap unless the human explicitly reprioritizes.

Do not pull backlog items into earlier PRs.

Expand All @@ -481,7 +482,11 @@ Do not pull backlog items into earlier PRs.
- Multithreaded gateway and market data pipeline, plus portable threaded TCP serving follow-up. (#26)
- ThreadSanitizer coverage. (#27)
- More realistic synthetic order-flow model. (#28)
- FIX-like text protocol adapter. (#29)
- FIX-like text protocol adapter. (#29) — **done**: `tag=value` SOH-framed adapter
(`include/qsl/protocol/fix.hpp`, `src/protocol/fix.cpp`) over the same internal structs as the
binary codec, with genuine FIX BeginString/BodyLength/CheckSum framing for NewOrderSingle (35=D)
and OrderCancelRequest (35=F). Cross-codec equivalence + malformed-input rejection tested in
`tests/unit/test_fix_protocol.cpp`; docs in `docs/fix_protocol.md`.
- Web dashboard for visualization. (#30)
- Docker packaging. (#31)
- Perf/flamegraph docs. (#32) — **done**: `make flamegraph` renders a perf call-graph flamegraph
Expand Down
23 changes: 21 additions & 2 deletions PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,16 @@ Do not rely on prior chat memory.
- **Next action:** no active milestone. Highest-value remaining work is non-code and gated:
issue #94 (independent external review — needs a human reviewer) and issue #90 (full
cache-counter PMU evidence — needs a PMU microarchitecture that exposes cache events, e.g.
x86_64). Low-signal backlog: #32 (flamegraph), #29 (FIX adapter).
x86_64). #29 (FIX-like text protocol adapter) is delivered in this PR
(`feat/fix-text-protocol-adapter`). Low-signal backlog: #32 (flamegraph).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove completed flamegraph from backlog

When resuming from this commit, this current-state anchor still tells agents that #32 (flamegraph) is remaining even though this stack is based on the flamegraph commit and the same file already records the flamegraph artifact as completed later in the history section. Because /resume is driven from these anchors, the next session can be sent back to a delivered issue; remove #32 from the backlog here or mark it as delivered in the current-state text.

Useful? React with 👍 / 👎.

- **Blockers:** issue #90 is now a *cache-counter* PMU gap, not a host-access gap — this bare-metal
Apple M2 exposes real `cycles`/`instructions`/`branches`/`branch-misses` but its PMU does not
implement `cache-references`/`cache-misses`; closing it needs a PMU microarchitecture that exposes
cache events (x86_64, or an ARM server core). Issue #94 remains open for independent external
review (human-gated). Hardware NIC/offload latency measurement still requires suitable wired NIC
hardware, driver support, timestamping/offload/RSS access, and a measured packet workload; the
current `wld0` Wi-Fi capability observation is not NIC-offload latency evidence. Legacy backlog
still includes #32 and #29. Issues #95, #28, and #26 were closed by PR #112.
still includes #32 (#29 delivered in this PR). Issues #95, #28, and #26 were closed by PR #112.

---

Expand Down Expand Up @@ -386,6 +387,24 @@ Lower priority:
the bare-metal Fedora Asahi host (aarch64) from the clean committed tree (`Dirty inputs: no`).
This is a software cpu-clock sampling hot-symbol profile, not a latency/throughput claim; full
hardware cache-PMU evidence stays in #90. Do not merge from automation; human squash-merges.
- [2026-06-21] Issue #29 FIX-like text protocol adapter (`feat/fix-text-protocol-adapter`, stacked
on the flamegraph branch). Added `include/qsl/protocol/fix.hpp` + `src/protocol/fix.cpp`: a
`tag=value` SOH-framed adapter over the SAME internal structs as the binary codec, with genuine
FIX framing (8 BeginString / 9 BodyLength / 35 MsgType / … / 10 mod-256 CheckSum) for the
client→gateway order path — NewOrderSingle (35=D)→`NewOrder` and OrderCancelRequest
(35=F)→`CancelOrder`. Decoding is total/deterministic/`noexcept` (fixed field table,
`std::from_chars`, `string_view`; no heap on decode) and reports every malformed input through a
`FixError` taxonomy mirroring the binary `DecodeError`. Documented, deliberate simplifications:
Symbol (55) carries the numeric SymbolId; Price (44) carries integer ticks and is always present,
making `NewOrder↔FIX` a lossless bijection like the binary codec (never float for price).
`tests/unit/test_fix_protocol.cpp` mirrors the binary required tests and adds a **cross-codec
equivalence** test (binary and FIX decode the same order to identical structs across all
Side×OrdType×TIF), a byte-pinned fixture (checksum 164 / body-length 50), and rejection of
malformed framing / unsupported BeginString / unknown-or-wrong MsgType / BodyLength mismatch /
CheckSum mismatch / missing field / invalid field / invalid enum / out-of-range / oversized. Docs
in `docs/fix_protocol.md` (+ pointer from `docs/binary_protocol.md`). `make check` 260/260 and
`make asan` 260/260 clean (the parser handles untrusted text). Closes #29. Do not merge from

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update resume anchors after closing #29

This entry marks #29 as closed, but the resume/current-state anchors still list #29 as open backlog in PROGRESS.md lines 47 and 55 and in HANDOFF.md lines 85, 93, and 110. Because /resume is explicitly driven from these files, the next session will be told to pursue the FIX adapter even though this PR just added it; update those anchors alongside this closure so the project state is not contradictory.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2abb9ca: #29 removed from the PROGRESS current-state and HANDOFF backlog lists and marked delivered in this PR. (The v0.2.1 release PR #133 then removes #32 too and marks both done.)

Comment on lines +405 to +406

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Refresh the top-level resume state

This new history entry records the FIX adapter as the latest work with make check/make asan at 260/260, but the Current state block above still says the last action was the earlier docs sync and that verification is 241/241. Since /resume reads that top block before the detailed history, the next session will start from stale action and test-count anchors; update the Current state fields alongside this completion entry.

Useful? React with 👍 / 👎.

automation; human squash-merges.
- [2026-06-03] M35: implemented a multi-client TCP connection-scaling load test (`scripts/socket_load.sh`, `make socket-load`, Linux-only) driving N concurrent `qsl-client`s against the portable TCP and epoll (M34) gateways; `results/socket_load_summary.txt` is Docker-generated and constrained. A `/code-review` (3 finder agents) caught and fixed real measurement-integrity bugs before the PR: a failed trial's `wall=0` no longer poisons the reported best (only trials whose gateway served count toward the min); the `completed` column reports the WORST per-trial completion, not the last, so partial/total trial failures are surfaced rather than masked; a per-client `timeout` bounds a hang if the gateway dies; and `QSL_LOAD_TRIALS` is validated. Post-PR hardening uses fresh monotonic ports per gateway start, retries transient startup/serve failures on new ports, and refuses to write a partial artifact unless `QSL_LOAD_ALLOW_PARTIAL=1` is set intentionally; the refreshed artifact records `Dirty tree: no`. The scaling-shape claim remains constrained to loopback connection setup, not a demonstrated production-capacity advantage for either transport. Deferred follow-up: a shared `scripts/lib` to remove the dirty-tree / `wait_ready` / gateway-stop duplication across the three socket scripts.
- [2026-06-03] M35: started after M34 (#98) squash-merged (commit 9e3750b). Scope: multi-client load / socket-pressure testing of the gateway/feed path (TCP/UDP stress, socket-buffer pressure, connection scaling, backpressure) building on M34's epoll multi-client path and M30's socket tooling. Constraints: scripts/tests document load shape + environment; results must distinguish kernel/socket pressure from user-space engine cost; no production-capacity claims (honest constrained-environment framing, like M29/M30).
- [2026-06-04] M35: PR #100 squash-merged to `main` as a86b701 after all CI jobs and review checks were green. M35 is now landed; original M36 NUMA remains deferred until the repository-health refactor analysis is completed or explicitly skipped by the human.
Expand Down
6 changes: 6 additions & 0 deletions docs/binary_protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,9 @@ a byte stream belong to the TCP/session layer (M9), not the codec.

The wire format is pinned by a byte-fixture test (`tests/unit/test_protocol.cpp`) so any
accidental change to field order or byte order fails the build.

## Text alternative

A human-readable, FIX-like `tag=value` adapter over the same internal message structs lives
alongside this binary codec — see [fix_protocol.md](fix_protocol.md). Both decode the same order to
identical structs, which the tests assert directly.
95 changes: 95 additions & 0 deletions docs/fix_protocol.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# FIX-like Text Protocol Adapter

A human-readable `tag=value` text protocol alongside the [binary protocol](binary_protocol.md),
mapping the **same internal message structs**. Implemented in `include/qsl/protocol/fix.hpp` and
`src/protocol/fix.cpp`; tested in `tests/unit/test_fix_protocol.cpp`. This is the optional FIX
adapter tracked by issue #29.

It is **FIX-like**, not a full FIX engine: it implements the genuine FIX framing and the
client→gateway order path, deliberately scoped to what mirrors the binary codec.

## Why it exists

Real venues speak FIX as well as binary protocols. Showing a second, independently-validated wire
format over one internal model demonstrates a clean protocol boundary: the engine does not care
which encoding produced a `NewOrder`. The strongest invariant the tests assert is that a binary
frame and a FIX message for the same order **decode to identical internal structs**.

## Framing

A message is a sequence of `tag=value` fields, each terminated by the **SOH** byte (`0x01`). The
adapter uses the standard FIX envelope:

```text
8=FIX.4.2 | 9=<BodyLength> | 35=<MsgType> | <business fields...> | 10=<CheckSum> |
```

(`|` denotes SOH.)

- **`8` BeginString** must be `FIX.4.2`; anything else is `UnsupportedBeginString`.
- **`9` BodyLength** is the byte count from the field after tag 9 through the SOH before tag 10.
A mismatch is `BodyLengthMismatch`.
- **`10` CheckSum** is the mod-256 sum of every byte up to the SOH before tag 10, formatted as
exactly three zero-padded digits. A mismatch is `ChecksumMismatch`.

## Messages

### NewOrderSingle (`35=D`) → `NewOrder`

| Tag | FIX name | Internal field | Encoding |
|-----|---------------|----------------|----------|
| 34 | MsgSeqNum | sequence no. | carried like the binary header `seq_no` (validated, not stored in the body struct) |
| 11 | ClOrdID | `order_id` | decimal |
| 55 | Symbol | `symbol` | decimal `SymbolId` (see simplifications) |
| 54 | Side | `side` | `1`=Buy, `2`=Sell |
| 38 | OrderQty | `quantity` | decimal |
| 40 | OrdType | `type` | `1`=Market, `2`=Limit |
| 44 | Price | `price` | integer ticks (see simplifications) |
| 59 | TimeInForce | `tif` | `1`=GTC, `3`=IOC |

### OrderCancelRequest (`35=F`) → `CancelOrder`

| Tag | FIX name | Internal field | Notes |
|-----|--------------|----------------|-------|
| 34 | MsgSeqNum | sequence no. | as above |
| 41 | OrigClOrdID | `order_id` | the order being cancelled |
| 11 | ClOrdID | — | required by FIX; validated on decode, echoes `order_id` on encode (no separate cancel-request id is modelled) |
| 55 | Symbol | `symbol` | decimal `SymbolId` |

## Deliberate simplifications

These are documented departures from strict FIX, chosen so the adapter stays a deterministic,
lossless map onto the simulator's internal model:

- **Symbol (tag 55) carries the numeric `SymbolId`** in decimal, not a ticker string — the engine
keys on `SymbolId`, so mapping to a string table would only add a lossy layer.
- **Price (tag 44) carries integer ticks and is always present**, including market orders. The
project never represents price as a float, and `NewOrder` always has a `price` field; carrying it
losslessly makes `NewOrder ↔ FIX` a true bijection over the internal struct, exactly like the
binary codec. (Strict FIX uses a decimal price and forbids tag 44 on market orders.)

## Error model

Decoding is total and deterministic: it never throws, allocates nothing on the decode path (a
fixed field table, `std::from_chars`, `std::string_view`), and reports every malformed input via
`FixError` rather than undefined behavior — mirroring the binary codec's `DecodeError` discipline.

`FixError`: `None`, `Malformed`, `UnsupportedBeginString`, `UnknownMsgType`, `MissingField`,
`InvalidField`, `BodyLengthMismatch`, `ChecksumMismatch`, `InvalidEnumValue`, `OutOfRange`.

## Determinism and testing

`tests/unit/test_fix_protocol.cpp` mirrors the binary codec's required tests and adds FIX-specific
ones:

- round-trip for NewOrderSingle and OrderCancelRequest;
- **cross-codec equivalence**: binary and FIX decode the same order to identical structs across all
Side × OrdType × TIF combinations;
- a **byte-pinned fixture** (`8=FIX.4.2|9=50|35=D|…|10=164|`) so any change to field order or the
checksum/body-length computation fails the build;
- rejection of malformed framing, unsupported BeginString, unknown/wrong MsgType, BodyLength
mismatch, CheckSum mismatch, missing required fields, non-numeric fields, invalid enum codes,
out-of-range integers, and oversized messages;
- signed/extreme `int64` price and `uint64` id/seq round-trips.

The adapter is also covered by the ASan/UBSan preset (`make asan`), since it parses untrusted text.
Loading
Loading