Aster Futures wire format
How Aster Perpetual Futures messages become rows in our Parquet files. Each data type below shows a real message next to the rows we publish for it, with the source and the rule for every column. Hover a line or a column to see how they connect.
Checked against our pipeline
These messages were captured from Aster Perpetual Futures on 2026-10-05. Our test suite replays each one through the production collector parser and postprocessor, and fails if the published rows differ from the rows on this page. Files written before a change to the pipeline can differ; see Known Gaps & Corrections.
Aster's market data API uses Binance USDⓈ-M Futures message formats, and the same conversions apply. Aster's documentation does not list the <symbol>@trade and <symbol>@depth@0ms streams the collector subscribes to; both send Binance-format trade and depthUpdate events.
- Trades WebSocket:
wss://fstream.asterdex.com/stream?streams=<symbol>@trade/… - Depth WebSocket:
wss://fstream.asterdex.com/stream?streams=<symbol>@depth@0ms/… - Ticker WebSocket:
wss://fstream.asterdex.com/stream?streams=!ticker@arr - Mark price WebSocket:
wss://fstream.asterdex.com/stream?streams=!markPrice@arr@1s - Liquidations WebSocket:
wss://fstream.asterdex.com/stream?streams=!forceOrder@arr - REST:
https://fapi.asterdex.com
Exchange API reference: github.com. Conventions shared by every exchange are on the wire formats overview.
Trades
One row per trade from the <symbol>@trade stream.
- WebSocket <symbol>@trade
{"stream": "btcusdt@trade","data": {"e": "trade","E": 1791227773292,→ event_time"T": 1791227773250,→ trade_time"s": "BTCUSDT",→ symbol"t": 148500896,→ trade_id"p": "85600.9",→ price"q": "0.002",→ quantity"X": "MARKET",→ order_type"m": false→ is_buyer_maker}}
Published row
true means the seller was the aggressor.MARKET if Aster omits the field.Notes
- Trades with a zero or negative
quantityare dropped and not published.
Column types and descriptions: Trades schema.
Order Book
One row per price level. Updates come from the <symbol>@depth@0ms stream; REST snapshots anchor the book.
- WebSocket <symbol>@depth@0ms
- REST GET /fapi/v1/depth
Depth update
{"stream": "btcusdt@depth@0ms","data": {"e": "depthUpdate",→ event_type"E": 1791227773380,→ event_time"T": 1791227773350,→ transaction_time"s": "BTCUSDT",→ symbol"U": 585621556224,→ first_update_id"u": 585621557091,→ final_update_id"pu": 585621555794,→ prev_final_update_id"b": [→ side["85590.6", "4.298"]→ side · price · quantity],"a": [→ side["85599.7", "0.000"],→ side · price · quantity["85604.3", "6.949"],→ side · price · quantity["85648.6", "0.000"]→ side · price · quantity]}}
Published rows · 4
update for every depthUpdate message.u; a mismatch is a gap.bid for levels in b, ask for levels in a. One row per level.0.000 removes the level.REST depth snapshot
The collector fetches a REST snapshot when it connects and when it has to re-anchor the book after a sequence gap. Only snapshots the collector applied to its book are published, as one snapshot row per level.
Abridged: Requested with limit=5 to keep the example short; the collector requests limit=500.
{"lastUpdateId": 585621541676,→ final_update_id · last_update_id"E": 1791227772674,→ event_time"T": 1791227772650,→ transaction_time"bids": [→ side["85600.8", "0.109"],→ side · price · quantity["85599.6", "0.058"],→ side · price · quantity["85594.3", "0.025"],→ side · price · quantity["85594.2", "0.263"],→ side · price · quantity["85594.1", "0.722"]→ side · price · quantity],"asks": [→ side["85600.9", "0.963"],→ side · price · quantity["85604.0", "0.125"],→ side · price · quantity["85604.1", "0.025"],→ side · price · quantity["85604.2", "6.206"],→ side · price · quantity["85604.3", "6.949"]→ side · price · quantity]}
Published rows · 10
snapshot for every REST snapshot.first_update_id ≤ this value ≤ final_update_id, or whose prev_final_update_id equals it.bid for levels in bids, ask for levels in asks. One row per level.Notes
- Aster can send a removal (quantity
0) for a level that is not in the book. Its documentation says this is normal; ignore it when you replay. - A depth update with no levels becomes one row with
sidenoopandpriceandquantity0. It carries the update IDs and does not change the book. An empty REST snapshot becomes onenoopsnapshotrow: clear the book on it. - A file can start with a checkpoint: our replayed book at the hour boundary, as
snapshotrows withreceived_timeat the hour start, theevent_timeof the last exchange event before it, nulltransaction_time, and the last applied update ID infinal_update_idandlast_update_id. A checkpoint is written only when the book is proven current. Without one, continue from the previous hour's book or wait for the next snapshot.
Column types and descriptions: Order Book schema.
Ticker
One row per symbol whose statistics changed, from the !ticker@arr stream, at most one per symbol per second.
- WebSocket !ticker@arr
The all-market frame carries only the tickers that changed in the last second; this one carried 2. Each entry becomes its own row.
{"stream": "!ticker@arr","data": [{"e": "24hrTicker","E": 1791227773292,→ event_time"s": "BTCUSDT",→ symbol"p": "254.9",→ price_change"P": "0.299",→ price_change_percent"w": "85953.6",→ weighted_average_price"c": "85600.9",→ last_price"Q": "0.002",→ last_quantity"o": "85346.0",→ open_price"h": "86964.9",→ high_price"l": "84942.4",→ low_price"v": "7310.230",→ base_asset_volume"q": "628340740.71",→ quote_asset_volume"O": 1791141360000,→ statistics_open_time"C": 1791227773250,→ statistics_close_time"F": 148456821,→ first_trade_id"L": 148500896,→ last_trade_id"n": 44075→ total_trades},{"e": "24hrTicker","E": 1791227772893,→ event_time"s": "TRADOORUSDT",→ symbol"p": "0.0106000",→ price_change"P": "1.529",→ price_change_percent"w": "0.6992235",→ weighted_average_price"c": "0.7038000",→ last_price"Q": "1",→ last_quantity"o": "0.6932000",→ open_price"h": "0.7055000",→ high_price"l": "0.6880000",→ low_price"v": "87356",→ base_asset_volume"q": "61081.3700000",→ quote_asset_volume"O": 1791147960000,→ statistics_open_time"C": 1791227772850,→ statistics_close_time"F": 384877,→ first_trade_id"L": 385481,→ last_trade_id"n": 605→ total_trades}]}
Published rows · 2
0.299 means +0.299 %.Notes
- A symbol whose statistics did not change gets no row for that second. Carry the last row forward.
Column types and descriptions: Ticker schema.
Mark Price
One row per symbol per !markPrice@arr@1s frame, about one per second.
- WebSocket !markPrice@arr@1s
Abridged: The all-market frame carried 771 symbols; only BTCUSDT is shown. Each entry becomes its own row.
{"stream": "!markPrice@arr@1s","data": [{"e": "markPriceUpdate","E": 1791227773000,→ event_time"s": "BTCUSDT",→ symbol"p": "85606.06731160",→ mark_price"P": "85585.57423237",→ estimated_settle_price"i": "85630.89804348",→ index_price"r": "0.00006494",→ funding_rate"T": 1791244800000→ next_funding_time}]}
Published row
0.00006494 is 0.006494 %.Column types and descriptions: Mark Price schema.
Liquidations
One row per forceOrder event on the !forceOrder@arr stream.
- WebSocket !forceOrder@arr
{"stream": "!forceOrder@arr","data": {"e": "forceOrder","E": 1791228276231,→ event_time"o": {"s": "PAIDUSDT",→ symbol"S": "SELL",→ side"o": "LIMIT",→ order_type"f": "IOC",→ time_in_force"q": "14270",→ quantity"p": "0.0037107",→ price"ap": "0.0040600",→ average_price"X": "FILLED",→ order_status"l": "14270",→ last_filled_quantity"z": "14270",→ filled_quantity"T": 1791228276200→ trade_time}}}
Published row
BUY closes a liquidated short, SELL a liquidated long.Notes
- Aster pushes at most one liquidation per symbol per 1,000 ms: the latest in each window. Rows are a sample, not every liquidation: counts and totals are a lower bound.
Column types and descriptions: Liquidations schema.
Not published
- Open Interest: The collector does not poll Aster's open interest, so no open interest files are published.