Skip to content

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

Open

Description

@Nexory

polymarket clob book <token> prints the bids and asks tables in raw API order (src/output/clob/books.rs:34-66). The API returns:

  • bids in ascending price order (lowest bid first)
  • asks in descending price order (highest ask first)

The CLI just iterates result.bids.iter() / result.asks.iter() straight into a tabled::Table, so what the user sees is:

Bids:
┌───────┬────────────┐
│ Price │ Size       │
├───────┼────────────┤
│ 0.001 │ 1218691.83 │   ← worst bid (top)
│ 0.002 │ 538822.20  │
│ ...   │            │
│ 0.008 │ 3905.98    │   ← BEST bid (bottom)
└───────┴────────────┘

Asks:
┌───────┬────────────┐
│ Price │ Size       │
├───────┼────────────┤
│ 0.999 │ 3139845.17 │   ← worst ask (top)
│ ...   │            │
│ 0.009 │ 312.07     │   ← BEST ask (bottom)
└───────┴────────────┘

(reproduced against the BTC $150k by June 30 market; same ordering verified against 2 other high-volume markets — the API behavior is consistent.)

Both best prices end up at the bottom of their respective tables, which is the opposite of what a CLI user usually wants to see first. The non-obvious ordering is also documented in Polymarket/rs-clob-client#330, which calls out the same mismatch in the V1 Rust SDK.

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

Suggested fix

Option A — minimal change (best-first per side): reverse bids (already-correct asks stay as-is so the best ask sits at the bottom adjacent to the best bid):

let rows: Vec<Row> = result.bids.iter().rev()  // best bid first
    .map(|o| Row { price: ..., size: ... })
    .collect();

Option B — depth-ladder layout: print asks first (descending → best ask at bottom of asks), then bids reversed (descending → best bid at top of bids), so the spread sits in the middle:

Asks:
  0.999
  ...
  0.009   ← best ask
──────── spread
Bids:
  0.008   ← best bid
  ...
  0.001

This matches the convention used by TradingView, Binance, Kraken, and most other exchange UIs.

Happy to send a small PR with whichever option fits the project direction.

Activity

  1. aion-blue-labs commented on Jun 20, 2026

    @aion-blue-labs

    This is worth treating as more than display polish. The same ordering pitfall can leak into bot/risk code if a caller accidentally treats the first array element as best price.

    A useful invariant for the CLI/tests would be:

    • best bid = max bid price, never bids[0] unless the API contract changes;
    • best ask = min ask price, never asks[0] unless the API contract changes;
    • human table should make those best levels visually adjacent or explicitly labelled;
    • JSON output should preserve raw API order for compatibility, as you noted.

    We hit the same class of bug in a pre-action risk gateway: the book looked like top_bid=0.001 / top_ask=0.999, which made otherwise liquid markets appear degenerate. A small regression fixture with intentionally reversed raw order would prevent this from reappearing in both display and downstream metrics.

  2. osr21 commented on Jul 23, 2026

    @osr21

    Confirming the display order issue. The CLOB REST API returns:
    - bids sorted ascending by price (lowest bid first, best bid last)
    - asks sorted descending by price (highest ask first, best ask last)

    So the raw API order puts the least competitive prices at the top of both tables — the opposite of every standard orderbook UI convention.
    
    The correct display for a typical L2 orderbook is:
    
    ```
    Asks (descending — worst ask at top, best ask at bottom, closest to mid):
    ┌────────┬──────────┐
    │ 0.72   │ 3 200    │   ← worst ask
    │ 0.71   │ 1 800    │
    │ 0.70   │  900     │   ← best ask  ← spread here
    └────────┴──────────┘
    Bids (descending — best bid at top, worst bid at bottom):
    ┌────────┬──────────┐
    │ 0.69   │ 1 100    │   ← best bid  ← spread here
    │ 0.68   │ 2 400    │
    │ 0.67   │  600     │   ← worst bid
    └────────┴──────────┘
    ```
    
    Fix in Rust: reverse the iterators before building the `tabled::Table`:
    
    ```rust
    // bids: API returns ascending → display descending (best at top)
    result.bids.iter().rev()
    
    // asks: API returns descending → display descending (worst at top, best just above spread)
    // already in correct display order — no reversal needed
    result.asks.iter()
    ```
    
    Note: PR #81 already addresses this. This comment adds the rationale for why asks don't need reversal (they're already in the right display order from the API).
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions