jonnify·bcs /bcs/trading/engine/price-time-by-mint-order

B8.1 · dive 3 — price-time by mint order

Price-time priority, without a comparator.

A matching engine resolves two crossing orders by price-time priority: best price first, and within a price, the order that arrived first. This platform's order book gets the second half for free. Exchange.OrderBook (PROPOSED, pure) is a price ladder per side — an ordered tree keyed by price, gb_trees per the house pattern — each level a FIFO resolved by branded mint order, "so price-time priority falls out of the id law rather than a comparator."

The order book is PROPOSED — the trading engine on top of a real substrate (docs/trading/), not a shipped module yet. It claims no fill count and no match latency; those land at its rung. The substrate beneath it is real and hardened: EchoCache.Ring is shipped Elixir in the live umbrella, the convergence target of the rung-gated EchoMQ program (docs/echo_mq/emq.roadmap.md), which names the trading platform as its downstream consumer; the mint-order law the book leans on is committed — Appendix F's order theorem, held in SQLite, Valkey, and Postgres COLLATE "C" alike.

The price-ladder matcher. A fixed ask ladder: two resting asks at 100 — ORD-a1 (older) then ORD-a3 (newer) — and one at 101, ORD-a2. An incoming buy crosses; the matcher fills best price first, and within 100 the smaller (older) mint id first. The comparator falls out of the id law.
ask ladder (best = lowest price; within a level, smallest mint id first) 100 ORD-a1 · 3 ORD-a3 · 4 mint order: a1 before a3 101 ORD-a2 · 5 incoming buy crosses the ladder; fills land best price first, then by mint id within a level price-time priority = (price from the gb_trees edge) + (time from the branded id) — no comparator written
ladder · asks: 100 → [ORD-a1 3, ORD-a3 4], 101 → [ORD-a2 5]. Send a buy — the matcher fills best price first, and within a level the smaller mint id first. The priority falls out of the ids, computed live.
§1

The ladder, and why mint order is the comparator

docs/trading/trading.specs.md · the pure book

The book is a price ladder per side: an ordered tree keyed by price (gb_trees), the bids descending and the asks ascending, so the best price is a tree edge. Within one price level, orders queue in a FIFO — and the FIFO key is the branded Snowflake stamped at admission. Mint order is arrival order, by construction, so "first at this price" is "smallest id at this price," and the same order holds in SQLite, Valkey, and Postgres COLLATE "C" alike by Appendix F's order theorem.

# Exchange.OrderBook (PROPOSED): a price ladder per side
# asks :: gb_trees keyed by price; each level a FIFO of branded ids
# best ask = the leftmost price (a tree edge), no scan
# within a level: oldest = smallest mint id = first filled

The consequence is a design with one ordering, not two: the sequence, the sort, the cursor, and the within-level priority are the same fourteen bytes, paid for once at mint. A separate sequence counter beside each level would be a second source of truth to keep in step with the first; the id removes it. This is the same move the ingress made in dive 1 — the chase sequence is the id — applied to the resting book.

§2

The open-order indexes ride the branded tree

docs/trading/trading.specs.md · EchoData.BrandedTree

Open-order indexes per account ride EchoData.BrandedTree — namespaced gb_trees with first/2, last/2, and page_after/4, "the oldest-first and pagination shapes a book needs already its verbs." A book asks for the oldest open order, or the next page after a cursor, and the branded tree answers in creation order without a sort step — the same page_after/4 the ingress consumer uses to resume from an id cursor in dive 1. Snapshots for projections freeze through the CHAMP / FrozenIndex family rather than copying mutable maps.

So the book and the ingress share one ordering verb. The id is the cursor on the ring, the FIFO key in the ladder, and the pagination key in the open-order index — three uses of the fourteen bytes the mint already stamped, with no comparator and no second counter anywhere in the engine.

§3

The single-writer reconcile (a PROPOSED gate)

docs/trading/trading.specs.md · TRD.2, design-voiced

The master invariant binds the two halves of the engine: one instrument's book is mutated by exactly one process — the sole drainer of its Ring — and the Ring's accepted count reconciles exactly with the Book's applied count plus the counted drops. Crossing orders produce fills; the publishes that were accepted equal the applies plus the drops, with nothing lost in between and nothing applied twice.

The single-writer reconcile. Over a fixed admission run against capacity 64, the readout reconciles publishes = applies + drops — the TRD.2 gate's shape. This is a PROPOSED gate: it reports the reconcile arithmetic, never a measured throughput. It becomes a committed record at its rung.
0accepted (publishes)
0applied (by the writer)
0dropped (counted)
reconcile · Publish a run, then let the writer apply. The gate holds when accepted = applied + dropped — the shape the harness will check at the Book's rung. No throughput is asserted here.

This is the TRD.2 gate, and it is PROPOSED — "the single-writer property under concurrent publishes" the design will gate at the rung that ships the Book. The reconcile is the shape the harness will check, not a number already on the record. The acknowledge-after-append rule is the spec's law: a command accepted then crashed before its events are appended is lost and the caller knows nothing, so the gate reconciles publishes against applies-plus-drops precisely to make that boundary visible.

§4

The seat the engine fills — the ring, real and quoted

echo/apps/echo_cache/lib/echo_cache/ring.ex · B4.3 record

The reconcile stands on a real, hardened seat. EchoCache.Ring — shipped Elixir at echo/apps/echo_cache/lib/echo_cache/ring.ex, the convergence target of the rung-gated EchoMQ program (/echomq, docs/echo_mq/emq.roadmap.md) that names the trading platform as its downstream consumer — already proves the admission half of the arithmetic: the accepted count and the counted drops are real today, its committed behavior on the B4.3 record:

content/echo_data/runtimes/elixir/bcs_rung_4_3_check.out · the admission side, committedfrozen
G4 full ok -- the bound held its shape: 64 accepted, 136 refused with :dropped and counted -- never blocked, never overwritten -- then the release drained all 64 and publish 201 landed and applied: a storm bends the lane's at-most-once contract no further than the contract already bends
G1 surface ok -- the ring's surface is whole -- ... a fresh ring stands at occupancy 0
PASS 6/6

The accepted count, the counted drops, and the drained applies are the three terms the PROPOSED Book's reconcile will balance — the ring's record fixes the ingress side today (64 accepted, 136 refused); the Book's rung will fix the apply side. That is the two-layer discipline in one paragraph: the seat is real, shipped, and quoted; the trading engine on top — the Exchange.* writer that fills it — is PROPOSED and design-voiced; and no fill count, match latency, or throughput is claimed for an engine that has run no rung yet, while the substrate beneath it already has its numbers. The next module, B8.2 · The Log and the Ledger, carries the facts these fills emit into the journal — Chapter 4.4's posture as its subject.

§5

References

sources · related

Sources

Related