Skip to content

fix(clob book): show best bid/ask at the top of the orderbook#81

Open
Nexory wants to merge 2 commits into
Polymarket:mainfrom
Nexory:fix/orderbook-best-prices-first-75
Open

fix(clob book): show best bid/ask at the top of the orderbook#81
Nexory wants to merge 2 commits into
Polymarket:mainfrom
Nexory:fix/orderbook-best-prices-first-75

Conversation

@Nexory
Copy link
Copy Markdown

@Nexory Nexory commented Jun 6, 2026

The change

Two .rev() calls — one on the bids iterator, one on asks — in src/output/clob/books.rs. The Polymarket CLOB API returns bids in ascending-price order (worst bid first) and asks in descending-price order (worst ask first), so iterating straight into tabled::Table buries the best prices at the bottom of each section. The ordering is now extracted into two small super-visible helpers (bids_in_display_order, asks_in_display_order) used by print_order_book, so the tests can exercise the production ordering directly without re-implementing it inline. JSON output, the type definitions, and every other display path are untouched.

@@ pub fn print_order_book(
+fn bids_in_display_order(bids: &[OrderSummary]) -> Vec<&OrderSummary> {
+    bids.iter().rev().collect()
+}
+
+fn asks_in_display_order(asks: &[OrderSummary]) -> Vec<&OrderSummary> {
+    asks.iter().rev().collect()
+}
@@
-                let rows: Vec<Row> = result
-                    .bids
-                    .iter()
+                let rows: Vec<Row> = bids_in_display_order(&result.bids)
+                    .into_iter()
                     .map(|o| Row { ... })
                     .collect();
@@
-                let rows: Vec<Row> = result
-                    .asks
-                    .iter()
+                let rows: Vec<Row> = asks_in_display_order(&result.asks)
+                    .into_iter()
                     .map(|o| Row { ... })
                     .collect();

Empirical verification (local, Windows + stable rustc 1.96.0)

Three unit tests are added to src/output/clob/books.rs. Each calls a production helper directly — there is no .iter().rev() chain in the test body, so the assertion fails if the helper stops reversing.

Running the full books module tests on the branch as it stands (both helpers reverse):

running 3 tests
test output::clob::books::tests::empty_inputs_produce_empty_output ... ok
test output::clob::books::tests::asks_in_display_order_puts_best_ask_first ... ok
test output::clob::books::tests::bids_in_display_order_puts_best_bid_first ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 82 filtered out

Then deleting .rev() only from bids_in_display_order (asks helper kept reversing) and re-running:

running 3 tests
test output::clob::books::tests::empty_inputs_produce_empty_output ... ok
test output::clob::books::tests::asks_in_display_order_puts_best_ask_first ... ok
test output::clob::books::tests::bids_in_display_order_puts_best_bid_first ... FAILED

failures:

---- output::clob::books::tests::bids_in_display_order_puts_best_bid_first stdout ----

thread '...' panicked at src\output\clob\books.rs:196:9:
assertion `left == right` failed: bids must be reversed for display so the highest-priced bid is on top
  left: [0.30, 0.40, 0.50]
 right: [0.50, 0.40, 0.30]

test result: FAILED. 2 passed; 1 failed; 0 ignored; 0 measured; 82 filtered out

And, symmetric, with .rev() removed only from asks_in_display_order (bids helper kept reversing):

running 3 tests
test output::clob::books::tests::empty_inputs_produce_empty_output ... ok
test output::clob::books::tests::bids_in_display_order_puts_best_bid_first ... ok
test output::clob::books::tests::asks_in_display_order_puts_best_ask_first ... FAILED

failures:

---- output::clob::books::tests::asks_in_display_order_puts_best_ask_first stdout ----

thread '...' panicked at src\output\clob\books.rs:214:9:
assertion `left == right` failed: asks must be reversed for display so the lowest-priced ask is on top
  left: [0.70, 0.60, 0.50]
 right: [0.50, 0.60, 0.70]

test result: FAILED. 2 passed; 1 failed; 0 ignored; 0 measured; 82 filtered out

That is the property each test actually pins.

Compliance

  • cargo fmt --check — exit 0
  • cargo clippy --all-targets -- -D warnings — exit 0
  • cargo test — full suite: 84 passed | 1 failed; the one failure (commands::upgrade::tests::detect_target_returns_valid_triple with Unsupported platform: windows/x86_64) reproduces unchanged on main and is unrelated to this PR (detect_target for Windows in src/commands/upgrade.rs).

Reproduction in the wild

Reproduced against the BTC $150k by June 30 market and 2 other high-volume markets — API ordering is consistent (bids ascending, asks descending), and the table layout buries the best prices at the bottom in every case. The non-obvious ordering is also documented in Polymarket/rs-clob-client#330 for the V1 Rust SDK.

JSON output (OutputFormat::Json) keeps the API order so programmatic consumers stay backward-compatible — this is purely the human-facing table.

Closes #75.

Nexory added 2 commits June 6, 2026 10:45
…rket#75)

The display iterated bids and asks in storage order — best prices ended up at
the bottom of the table. Reverse the iteration order so the maker side stays
at the top, matching how every other CLOB UI presents the book.

Closes Polymarket#75.
The previous tests defined their own `.iter().rev()` chain via
`rendered_bid_prices` / `rendered_ask_prices` instead of exercising the
production iterator, so removing `.rev()` from `print_order_book` would
still leave the tests green — a tautology.

This commit extracts the ordering into two thin functions,
`bids_in_display_order` and `asks_in_display_order`, both used by
`print_order_book`. Tests now call these helpers directly. Removing
`.rev()` from either helper makes the corresponding test fail with a
verbatim wrong-order assertion, which is the property we want pinned.

Empirical verification:
  - HEAD (with .rev()):            3 passed; 0 failed
  - bids helper without .rev():    2 passed; 1 failed (bids test)
  - asks helper without .rev():    2 passed; 1 failed (asks test)
  - cargo fmt --check:             exit 0
  - cargo clippy --all-targets -- -D warnings: exit 0

An empty-input case is added so the helpers are also pinned against
panics on empty bids/asks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

polymarket clob book: orderbook table shows worst prices first (best bid/ask buried at bottom)

1 participant