Skip to content

Order book, ticker and trades

Three topics carry market data, the same for every participant, in the same wire format as the account channels and the REST API.

Topic Carries Filters
orderbook aggregated book levels market_ids (required)
ticker per-market price summary sports, competitions
trades executed trades, anonymous market_ids, event_ids

Each is a single topic covering every market, narrowed by the join payload. Watching ten markets is one join, not ten.

Money is a dollar string ("0.3900"), quantities are quantity strings ("100.00"), and counts are plain integers. Field names are snake_case throughout, and the offer side is called offer, not ask, the same names the REST market payload uses.

Every filter follows one contract:

  • omitted, null, [], or a list with no usable entry means no filter;
  • unusable entries are dropped rather than rejecting the join;
  • the join reply echoes what was actually applied, so compare it against what you sent to catch a typo;
  • naming two filters narrows: a message must match both;
  • select_filters (or select_market_ids on orderbook) changes them without rejoining.

orderbook is the one exception: it requires at least one valid market_id.

["1","1","orderbook","phx_join",{"market_ids":["<uuid>","<uuid>"]}]

An absent or unusable list is an error, not “no filter”:

{"status":"error","response":{"reason":"market_ids_required"}}

Pushes "book" on the matching engine’s publish cadence, one message per market:

{
"market_id": "...",
"bids": [{"price":"0.3900","quantity":"100.00","liquidity":"39.0000",
"total_quantity":"100.00","total_liquidity":"39.0000"}],
"offers": [],
"timestamp": "2026-09-02T21:16:39.717812Z",
"timestamp_us": 1788383799717812
}

Levels are best-first. liquidity is that level alone; total_quantity and total_liquidity are cumulative through it.

Change markets without rejoining:

["1","2","orderbook","select_market_ids",{"market_ids":["<uuid>"]}]

The non-empty requirement still applies; an unusable list leaves the current selection in place and replies with an error.

["1","1","ticker","phx_join",{}]
["1","1","ticker","phx_join",{"sports":["Football"],"competitions":["NFL"]}]

Pushes "ticker" for each market whose price, book top, volume or open interest moved:

{
"market_id": "...", "market_symbol": "STXNFL-...",
"event_id": "...", "event_symbol": "STXNFL-...",
"sport": "Football", "competition": "NFL",
"last_traded_price": "0.3900", "last_traded_quantity": "100.00",
"best_bid": "0.3800", "best_bid_quantity": "250.00",
"best_offer": "0.4000", "best_offer_quantity": "175.00",
"bid_depth": 4, "offer_depth": 6,
"open_interest": "1200.00", "total_volume": "48000.00",
"timestamp": "...", "timestamp_us": 1788383799717812
}

bid_depth and offer_depth count price levels, so they are integers rather than quantity strings. Any field can be null on a market that has not traded or has an empty side of the book. event_symbol is null while the event list is still warming.

Filter values are matched exactly as the market carries them, so "Football" and "football" are different.

["1","1","trades","phx_join",{}]
["1","1","trades","phx_join",{"market_ids":["<uuid>"],"event_ids":["<uuid>"]}]

Pushes "trade", one per execution:

{
"market_id": "...", "market_symbol": "STXNFL-...",
"event_id": "...", "event_symbol": "STXNFL-...",
"price": "0.3200", "quantity": "100.00", "action": "buy",
"timestamp": "...", "timestamp_us": 1788383799717812
}

action is the taker’s side: "buy" when the incoming order bought from the book, "sell" when it sold into it.

This feed is anonymous: it carries no account, user or order identifier for any market participant.

v1.5.9Changelogllms.txtllms-full.txt