Price history (market_stats)
Topic: market_stats
Price over time for the markets you name, as a series of buckets. This is the feed behind a price chart: the whole history arrives in the join reply, then only changed buckets are pushed.
One topic covers every market, narrowed by the join payload. Ten markets is one join, not ten.
Joining
Section titled “Joining”["1","1","market_stats","phx_join",{"market_ids":["<uuid>","<uuid>"]}]["1","1","market_stats","phx_join",{"market_ids":["<uuid>"],"range":"week"}]At least one valid market_id is required. Unlike the per-account filters on
this socket, an absent or unusable list is an error rather than “no filter”: the
full history of every market on the exchange is not something this serves.
{"status":"error","response":{"reason":"market_ids_required"}}range sets how far back the join snapshot reaches: "day", "week", "month"
or "all" (the default). An unknown value falls back to "all". The reply echoes
the filter and range, and carries the series:
{"status":"ok","response":{ "selected_market_ids":["<uuid>"], "range":"all", "markets":[{"market_id":"<uuid>","points":[ {"timestamp_us":1789671360000000,"price_percent":43.5}]}]}}A market id naming no market is echoed in selected_market_ids but contributes
no entry to markets.
Points
Section titled “Points”price_percent is the bucket’s closing price as a percent of that market’s
max_price, so a plain JSON number from 0.0 to 100.0 rather than money.
It is not probability from the market payload, which is a modeled value from
the pricing feed.
timestamp_us is the bucket’s start, in Unix microseconds. Points ascend by it,
and a bucket with no price is omitted rather than sent as zero.
No volume is carried: this is a price series. Per-market traded volume is
total_volume on ticker.
Server pushes
Section titled “Server pushes”market_statscarries{"market_id":"...","points":[point]}, the buckets that changed. A delta, not a snapshot. Buckets are 60 seconds wide but flush every 2 seconds or so, so the sametimestamp_usis re-sent as the current minute fills: upsert bytimestamp_usrather than appending.market_stats_snapshothas the same shape and replaces that market’s series wholesale. Sent after STX cancels a trade, which rewrites buckets already delivered and cannot be reconciled from a delta. Sent even when the series is now empty.
Changing which markets stream
Section titled “Changing which markets stream”["1","2","market_stats","select_market_ids",{"market_ids":["<uuid>"]}]The same non-empty requirement applies; an unusable list leaves the current
selection in place and replies with an error. This changes the subscription only
and sends no series, so adding a market cannot overwrite a window you set with
request_series. Fetch the new market’s history with that instead.
Fetching history at another range
Section titled “Fetching history at another range”["1","3","market_stats","request_series",{"market_ids":["<uuid>"],"range":"day"}]Replies with series for exactly those markets at that range:
{"status":"ok","response":{"range":"day","markets":[{"market_id":"<uuid>","points":[]}]}}The subscription is untouched. Range belongs to one history request rather than to the socket, so a client drawing three markets over three windows is one join plus three of these. The ids need not be subscribed, since price history is the same for every participant.
Live deltas are range-independent: every bucket update for a subscribed market is pushed whatever range was last requested.
Use Cases
Section titled “Use Cases”| Use case | Message to send |
|---|---|
| Join for two markets | ["3","3","market_stats","phx_join",{"market_ids":["<uuid>","<uuid>"]}] |
| Join with a week of history | ["3","3","market_stats","phx_join",{"market_ids":["<uuid>"],"range":"week"}] |
| Change which markets stream | ["3","4","market_stats","select_market_ids",{"market_ids":["<uuid>"]}] |
| Fetch one day for a chart | ["3","5","market_stats","request_series",{"market_ids":["<uuid>"],"range":"day"}] |
| Keep the channel alive | ["3","6","market_stats","ping",{}] |

