Skip to content

feat(trade): order book grouping, size units, layouts and conformance suite (OB-050, OB-052, OB-053) - #819

Merged
IbrahimIjai merged 6 commits into
SO4-Markets:mainfrom
precious-akpan:feat/702-704-705-conformance-grouping-layouts
Sep 26, 2026
Merged

IbrahimIjai merged 6 commits into
SO4-Markets:mainfrom
precious-akpan:feat/702-704-705-conformance-grouping-layouts

Conversation

@precious-akpan

@precious-akpan precious-akpan commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #818

This branch is built on the OB-049 work in #818, because the conformance suite uses its freshness module. Until #818 merges, this PR's diff also shows #818's two commits (2e6bcdf, c1401d0). The new work is the last four commits: eb6c546, 700279b, e3d247b and the changelog entry.

Summary

Implements OB-050 (#702), OB-052 (#704) and OB-053 (#705), now unblocked by the merged reducer and depth work (#817).

#704: grouping and unit controls

  • Grouping is offered as multiples of the market's native tick and stored per market as a multiplier, so switching between markets with different tick sizes keeps each choice. Stored values are sanitized against the market's current metadata. A grouping the market no longer offers is reset, and unusable metadata offers none.
  • Quote-unit sizes are exact (price x size carries priceScale + sizeScale decimals). Bucket prices never look better than a level they contain. Spread comes from the canonical book, so grouping cannot move it.
  • The store caps remembered markets, recovers from corrupt storage and holds no market data.

#705: layouts

  • Both sides, bids only and asks only allocate rows deterministically (an odd row goes to asks, and both never drops a side). Rows stay best-first from the spread, and depth bars scale against what is visible.
  • DepthPanel keeps the market identity, spread and freshness badge in every layout. Rows are keyed by price and there are no transitions, so re-sorting does not shimmer. A test drives 20 rapid switches and asserts the live feed component mounts exactly once.

#702: conformance suite (lib/orderbook/conformance/)

  • 47 deterministic scenarios: duplicates, disconnect and reconnect, missing revisions, partial fills and cancellation races, plus seeded randomized convergence. The expected book comes from an independent oracle. Every arrival permutation of each cancel/fill race is checked.
  • Mutation checks: breaking the reducer's gap check, duplicate check or reset, or making stage derivation call a partial fill filled, each makes the suite fail.
  • README.md names the tested modules with revisions, the schema covered and what is mocked versus not verified.

What is not done or not verified

  • Not wired into the live OrderBookPanel. That panel intentionally shows reference data only until a verified matching source exists, and there is no market-metadata source in the repo, so the controls take metadata as input rather than inventing tick sizes. Mounting them is a follow-up once both exist.
  • All conformance evidence is mocked. Fixtures are hand-built and nothing was verified against a live venue or the indexer. Order and fill events go through a test-support reference fold because the app does not yet consume them as a stream. The README says all of this.
  • No book-revision-to-order linkage. The indexer's Order entity carries ledgers and timestamps, not book revisions, so there is nothing to test ("where the source exposes that relationship").
  • Motion skills: OB-052: Add precision grouping and unit controls #704 and OB-053: Add both-sides, bids-only, and asks-only views #705 require the animation skill files, which the issues say are not on main, and the repo has none. This PR adds no animation, and it removes the ladder's width transition in the new panel, so there are no clips or review table to attach.

Test evidence

  • 156 new tests pass: display-prefs 42, layout 20, store 22, DepthPanel 25, conformance 47. (feat(trade): expose order book source health and freshness (OB-049) #818's 65 tests are separate.) They cover exact aggregation fixtures, keyboard use of the layout radios, per-market memory, reset on lost precision, no remount and no transitions.
  • eslint is clean on every new file. tsc reports nothing in them. No new check:tokens violations.

Gate status (AGENTS.md section 1), reported honestly

The gate does not pass on upstream main independent of this change:

  • bun typecheck and bun run build fail on syntax errors in files this PR does not touch (useOrderEventPolling.ts, TVChartContainer.tsx, TradePanel.tsx, useLiveBar.ts).
  • check:tokens reports 5 existing violations in DepthChart.tsx and DepthLadder.tsx.
  • The trade feature has a set of failing test files with and without this change. The failures I checked are unrelated, and CollateralDialog.test.tsx failed once under load and passes alone with and without my changes.
  • Not run: test:coverage and the indexer integration steps, since they sit behind the failing steps.

Closes #702
Closes #704
Closes #705

The order book feed had only a connection status, so a healthy but quiet
market and a dead transport were indistinguishable, and nothing stopped
stale depth from being treated as current.

Track separately: transport state, the time of the last frame of any kind
(heartbeats and acks count), the last valid book revision, and the provider
timestamp of each update. Derive fresh / stale / reconnecting / unavailable
with an explicit reason: silence past the heartbeat timeout, a run of
malformed frames, or a provider delivering updates late are stale; a long
gap with no book change while frames keep arriving is a quiet market and
stays fresh. Provider lag is measured against a rolling baseline of
delivery delays, so a constant clock offset in either direction is not lag.
Duplicate or out-of-order revisions never make a book look fresher.

quoteFromDepth() is the only sanctioned way to derive a suggested price from
depth and returns null unless the feed is fresh with an applied snapshot.

The shared subscription now records this from the frames it receives, and
the order book panel shows the existing source health badge.

Verification: 42 pure-logic tests, 11 stream tests through a fake WebSocket
with an explicit MSW snapshot handler, 9 hook tests and 3 panel tests, all
passing. These are mocked-transport tests; no live Binance verification.
The repository gate (lint, typecheck, check:tokens, tests, build) does not
pass on upstream main independent of this change: a syntax error in
useOrderEventPolling.ts breaks typecheck and build, and pre-existing lint,
token and test failures remain. The failing test set is identical with and
without this change.
…s (OB-052, OB-053)

Pure, exact-integer logic for the depth view's display controls, plus a
persisted store.

Grouping is offered as multiples of the market's native tick and stored per
market as a multiplier, so switching between markets with different tick
sizes keeps each market's own choice. Stored values are never trusted: they are
sanitized against the market's current metadata, a value the market no longer
offers is reset (and reported), and unusable metadata offers no grouping at
all. Quote-unit sizes are exact (price x size carries priceScale + sizeScale
decimals), and bucket prices never appear better than a level they contain.

Layouts (both, bids only, asks only) allocate rows deterministically, keep
rows best-first from the spread, and scale depth bars against what is actually
visible. The store caps remembered markets, recovers from corrupt storage and
holds no market data.

There is no market-metadata source in the repo yet, so these take metadata as
input rather than inventing tick sizes.
DepthControls uses the shared Select and RadioGroup, with explicit accessible
names carried in visually hidden label text (the shared radio links itself to
its wrapping label, which overrides aria-label). DepthPanel shows the market
identity, spread and freshness badge in every layout, keys rows by price, and
has no transitions on the ladder so re-sorting rows does not shimmer.

The panel receives levels as props from whatever owns the live subscription, so
switching layout, grouping or unit only recomputes a display view and cannot
remount the feed; a test drives 20 rapid switches and asserts one mount. When
a market's metadata becomes unusable a stored grouping is reset rather than
kept.

Not wired into OrderBookPanel: that panel intentionally shows reference data
only until a verified matching source exists, and there is no market-metadata
source yet. No new motion was added, so the animation skills named in the
issue (not present in the repository) were not needed.
47 deterministic scenarios over the production book reducer and order
lifecycle derivation: duplicates, disconnect and reconnect, missing revisions,
partial fills and cancellation races, plus seeded randomized convergence. The
expected book comes from an independent oracle rather than the reducer under
test, and every arrival permutation of each cancel/fill race is checked.

Mutation checks confirm it has teeth: breaking the reducer's gap check,
duplicate check or reset, or making the stage derivation call a partial fill
filled, each fails the suite.

README.md names the tested modules and their revisions and separates what is
mocked from what is not verified. All fixtures are hand-built; nothing was
verified against a live venue or indexer, and order/fill events go through a
test-support reference fold because the app does not yet consume them as a
stream.
@vercel

vercel Bot commented Sep 26, 2026

Copy link
Copy Markdown

@precious-akpan is attempting to deploy a commit to the Ijai's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Sep 26, 2026

Copy link
Copy Markdown

@precious-akpan Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@IbrahimIjai
IbrahimIjai merged commit 5576426 into SO4-Markets:main Sep 26, 2026
1 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants