Bybit Futures wire format
How Bybit derivatives, linear contracts (USDT and USDC perpetuals and 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 Bybit derivatives, linear contracts (USDT and USDC perpetuals and 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.
The collector subscribes to orderbook.50.<symbol>, publicTrade.<symbol>, tickers.<symbol> and allLiquidation.<symbol> for every trading linear contract. Ticker, mark price and open interest rows all come from the tickers topic.
- Public WebSocket:
wss://stream.bybit.com/v5/public/linear - REST (trade backfill):
https://api.bybit.com
Exchange API reference: bybit-exchange.github.io. Conventions shared by every exchange are on the wire formats overview.
Trades
One row per trade from the publicTrade.<symbol> topic, plus trades recovered from REST after a reconnect.
- WebSocket publicTrade.<symbol>
- REST GET /v5/market/recent-trade
Trade message
Bybit batches trades: this message carries 2, and each becomes its own row.
{"topic": "publicTrade.BTCUSDT","type": "snapshot","ts": 1791227620120,→ event_time"data": [{"T": 1791227620119,→ trade_time"s": "BTCUSDT",→ symbol"S": "Buy",→ is_buyer_maker"v": "0.001",→ quantity"p": "85629.30",→ price"L": "PlusTick","i": "caa63c3c-3c80-540f-8364-ea18a6824a70",→ trade_id"BT": false,"RPI": false,"seq": 820927598403},{"T": 1791227620119,→ trade_time"s": "BTCUSDT",→ symbol"S": "Buy",→ is_buyer_maker"v": "0.038",→ quantity"p": "85629.30",→ price"L": "ZeroPlusTick","i": "e629c952-d70a-5354-8ace-495767ec2770",→ trade_id"BT": false,"RPI": false,"seq": 820927598404}]}
Published rows · 2
ts, when Bybit generated the push. Milliseconds. Shared by every trade in the message.0x1000001b3 instead of the standard FNV prime, then the sign bit cleared. The same UUID always gives the same ID.true when the taker side S is Sell, false when it is Buy.Market. A placeholder: Bybit sends no order type.REST trade backfill
Bybit does not replay trades on subscribe. After every trade connection (re)opens, the collector fetches the latest trades from GET /v5/market/recent-trade and publishes those it did not receive over the WebSocket. A backfilled trade whose trade_time falls outside the hour of the file is not published.
Abridged: Requested with limit=1 to keep the example short; the collector requests limit=1000, the most Bybit returns.
{"retCode": 0,"retMsg": "OK","result": {"category": "linear","list": [{"execId": "996d7ec5-7333-5567-a813-a4db019fcec2",→ trade_id"symbol": "BTCUSDT",→ symbol"price": "85743.50",→ price"size": "0.002",→ quantity"side": "Buy",→ is_buyer_maker"time": "1791227915662",→ event_time · trade_time"isBlockTrade": false,"isRPITrade": false,"seq": "820929204227"}]},"retExtInfo": {},"time": 1791227915757}
Published row
time: REST responses have no push time, so event_time equals trade_time on backfilled rows. Milliseconds.0x1000001b3 instead of the standard FNV prime, then the sign bit cleared. The same UUID always gives the same ID.true when the taker side side is Sell, false when it is Buy.Market. A placeholder: Bybit sends no order type.Notes
- Rows are sorted by
trade_time, then Bybit's cross sequenceseq. - The block-trade flag
BT, the RPI flagRPI, the tick directionLandseqare not published. Block trades and RPI trades are published as ordinary rows. - A trade received over both the WebSocket and the REST backfill is published once, with the WebSocket values. On a backfilled row
event_timeequalstrade_time, andreceived_timecan be seconds after the trade. - Bybit's REST endpoint returns at most the latest 1,000 trades, so a long reconnect gap on a busy contract is only partly recovered.
Column types and descriptions: Trades schema.
Order Book
One row per price level from the orderbook.50.<symbol> topic, published as Bybit sent it.
- WebSocket orderbook.50.<symbol>
Book snapshot
Bybit sends a snapshot of the 50-level book when the subscription starts, and again after a problem on its side or a service restart (u = 1). Each level becomes a snapshot row.
Abridged: Bybit sent 50 bid and 50 ask levels; the best three of each are shown.
{"topic": "orderbook.50.BTCUSDT",→ symbol"type": "snapshot",→ event_type"ts": 1791227613628,→ event_time"data": {"s": "BTCUSDT","b": [→ side["85626.10", "6.857"],→ side · price · quantity["85626.00", "0.004"],→ side · price · quantity["85625.90", "0.415"]→ side · price · quantity],"a": [→ side["85626.20", "1.565"],→ side · price · quantity["85626.30", "0.392"],→ side · price · quantity["85626.50", "0.001"]→ side · price · quantity],"u": 211266072,→ final_update_id"seq": 820927562650→ last_update_id},"cts": 1791227613626→ transaction_time}
Published rows · 6
orderbook.50.BTCUSDT). Equals data.s.snapshot for type: "snapshot": every level of the 50-level book. A message with u = 1 (Bybit service restart) is also a snapshot.u + 1.seq. It orders messages but is not contiguous.bid for levels in b, ask for levels in a. One row per level.Book delta
{"topic": "orderbook.50.BTCUSDT",→ symbol"type": "delta",→ event_type"ts": 1791227613708,→ event_time"data": {"s": "BTCUSDT","b": [→ side["85619.20", "0.023"],→ side · price · quantity["85618.80", "0"]→ side · price · quantity],"a": [→ side["85626.20", "1.155"],→ side · price · quantity["85635.70", "0.005"],→ side · price · quantity["85636.20", "0.003"]→ side · price · quantity],"u": 211266074,→ first_update_id · final_update_id"seq": 820927562975→ last_update_id},"cts": 1791227613706→ transaction_time}
Published rows · 5
orderbook.50.BTCUSDT). Equals data.s.update for type: "delta".u. Each delta covers exactly one update ID, so first_update_id = final_update_id.u; a larger step is a gap.seq. It orders messages but is not contiguous.bid for levels in b, ask for levels in a. One row per level.0 removes the level.Notes
- The collector subscribes to the 50-level book only, and WebSocket snapshots anchor it. Bybit's REST depth snapshots carry the update IDs of its 1,000-level stream, so they cannot anchor this book and are not published.
- The collector checks that each delta's
uis one more than the previous message's before it writes. After a gap it reconnects and starts again from a new WebSocket snapshot. - A delta with no levels is published as one row with
side=noopandpriceandquantity0, so the update-ID chain has no holes. Skip these rows when rebuilding the book. - Files can start with a checkpoint snapshot of the book carried over from the previous hour: its
received_timeis the hour start,transaction_timeis null,final_update_idis the lastuapplied andlast_update_iditsseq. - Quantities are in base asset units (BTC for BTCUSDT). Bybit does not include Retail Price Improvement (RPI) orders in the book stream.
- Files written before the October 2026 pipeline update can also contain
snapshotrows built from REST depth snapshots. Their update IDs belong to Bybit's 1,000-level stream and do not continue the 50-level chain; skip them when replaying.
Column types and descriptions: Order Book schema.
Ticker
One row per tickers.<symbol> message that carries a field of the ticker row (lastPrice, prevPrice24h, price24hPcnt, highPrice24h, lowPrice24h, volume24h or turnover24h), holding the merged full state.
- WebSocket tickers.<symbol>
Ticker snapshot
Bybit sends one snapshot with every field when the subscription starts. It publishes a ticker, a mark price and an open interest row. In our captures the first snapshot was up to 2 seconds old on arrival, and the deltas since then followed at once.
{"topic": "tickers.BTCUSDT","type": "snapshot","data": {"symbol": "BTCUSDT",→ symbol"tickDirection": "ZeroPlusTick","price24hPcnt": "0.004251",→ price_change_percent"lastPrice": "85721.40",→ price_change · last_price"prevPrice24h": "85358.50",→ price_change · open_price"highPrice24h": "86986.50",→ high_price"lowPrice24h": "84907.70",→ low_price"prevPrice1h": "85436.60","markPrice": "85730.36","indexPrice": "85768.04","openInterest": "57163.259","openInterestValue": "4900626772.84","turnover24h": "5739447395.0800",→ quote_asset_volume"volume24h": "66763.0850",→ base_asset_volume"fundingIntervalHour": "8","fundingCap": "0.00333","singleOpenInterest": "28581.63","singleOpenInterestValue": "2450313429.29","nextFundingTime": "1791244800000","fundingRate": "0.00001336","bid1Price": "85721.30","bid1Size": "2.542","ask1Price": "85721.40","ask1Size": "10.152","preOpenPrice": "","preQty": "","curPreListingPhase": ""},"cs": 820929695495,"ts": 1791227990283→ event_time}
Published row
lastPrice − prevPrice24h, computed exactly in decimal.0.004251 becomes 0.4251.prevPrice24h, the price 24 hours ago.Ticker delta
A delta holds only the fields that changed. The row is the merged state: changed fields from this delta, the others carried forward. Replayed after the snapshot and the 21 deltas Bybit sent between them; those prepare the state and their rows are not shown.
Replayed after 22 earlier messages (not shown) that set the state this message builds on.
{"topic": "tickers.BTCUSDT","type": "delta","data": {"symbol": "BTCUSDT",→ symbol"tickDirection": "MinusTick","price24hPcnt": "0.00425",→ price_change_percent"lastPrice": "85721.30",→ price_change · last_price"turnover24h": "5739467196.7231",→ quote_asset_volume"volume24h": "66763.3160",→ base_asset_volume"bid1Price": "85721.30","bid1Size": "5.212"},"cs": 820929708680,"ts": 1791227992883→ event_time}
Published row
lastPrice − prevPrice24h, computed exactly in decimal from the merged state. prevPrice24h is not in this message and is carried forward.0.00425 becomes 0.425.Delta with no ticker field
Most deltas only move the best bid and ask (bid1Price, ask1Price and sizes), which are not published. A delta that carries no field of a data type publishes no row for it; this one publishes nothing. Replayed after the snapshot and the 17 deltas Bybit sent between them; those prepare the state and their rows are not shown.
Replayed after 18 earlier messages (not shown) that set the state this message builds on.
{"topic": "tickers.BTCUSDT","type": "delta","data": {"symbol": "BTCUSDT","bid1Price": "85721.30","bid1Size": "5.835","ask1Price": "85721.40","ask1Size": "0.744"},"cs": 820929705226,"ts": 1791227992283}
Not published
Notes
- Bybit sends one
snapshotand thendeltamessages that hold only the fields that changed. The pipeline folds every message into the symbol's full state and publishes the merged state, so unchanged fields are filled from earlier messages. Asnapshotreplaces the state. - The state at the end of an hour carries into the next hour. When the previous hour's state is not available (first run, lost state, or an hour without
tickersmessages), an hour holds only the fields seen in it until the next snapshot. Files before the 2026-09 data-integrity release hold change-only rows with nulls for unchanged fields. price_change_percentis in percent from the 2026-09 data-integrity release; earlier files hold Bybit's fraction.
Column types and descriptions: Ticker schema.
Mark Price
One row per tickers.<symbol> message that carries a mark price, index price or funding field, holding the merged full state.
- WebSocket tickers.<symbol>
Ticker snapshot
Bybit sends one snapshot with every field when the subscription starts. It publishes a ticker, a mark price and an open interest row. In our captures the first snapshot was up to 2 seconds old on arrival, and the deltas since then followed at once.
{"topic": "tickers.BTCUSDT","type": "snapshot","data": {"symbol": "BTCUSDT",→ symbol"tickDirection": "ZeroPlusTick","price24hPcnt": "0.004251","lastPrice": "85721.40","prevPrice24h": "85358.50","highPrice24h": "86986.50","lowPrice24h": "84907.70","prevPrice1h": "85436.60","markPrice": "85730.36",→ mark_price"indexPrice": "85768.04",→ index_price"openInterest": "57163.259","openInterestValue": "4900626772.84","turnover24h": "5739447395.0800","volume24h": "66763.0850","fundingIntervalHour": "8","fundingCap": "0.00333","singleOpenInterest": "28581.63","singleOpenInterestValue": "2450313429.29","nextFundingTime": "1791244800000",→ next_funding_time"fundingRate": "0.00001336",→ funding_rate"bid1Price": "85721.30","bid1Size": "2.542","ask1Price": "85721.40","ask1Size": "10.152","preOpenPrice": "","preQty": "","curPreListingPhase": ""},"cs": 820929695495,"ts": 1791227990283→ event_time}
Published row
predictedDeliveryPrice, which Bybit documents for dated futures. BTCUSDT is a perpetual and its messages do not carry the field, so it is null.0.0001 is 0.01 %.Ticker delta with a new index price
Any delta with markPrice, indexPrice, fundingRate, nextFundingTime or predictedDeliveryPrice publishes a mark price row from the merged state. Replayed after the snapshot and the 16 deltas Bybit sent between them; those prepare the state and their rows are not shown.
Replayed after 17 earlier messages (not shown) that set the state this message builds on.
{"topic": "tickers.BTCUSDT","type": "delta","data": {"symbol": "BTCUSDT",→ symbol"indexPrice": "85768.02",→ index_price"turnover24h": "5739466939.5592","volume24h": "66763.3130","ask1Price": "85721.40","ask1Size": "0.740"},"cs": 820929704683,"ts": 1791227992183→ event_time}
Published row
predictedDeliveryPrice, which Bybit documents for dated futures. BTCUSDT is a perpetual and its messages do not carry the field, so it is null.Notes
- Bybit sends one
snapshotand thendeltamessages that hold only the fields that changed. The pipeline folds every message into the symbol's full state and publishes the merged state, so unchanged fields are filled from earlier messages. Asnapshotreplaces the state. - The state at the end of an hour carries into the next hour. When the previous hour's state is not available (first run, lost state, or an hour without
tickersmessages), an hour holds only the fields seen in it until the next snapshot. Files before the 2026-09 data-integrity release hold change-only rows with nulls for unchanged fields. - Bybit's funding interval differs per contract (
fundingIntervalHour, 8 hours for BTCUSDT here). Neither it nor the funding capfundingCapis published.
Column types and descriptions: Mark Price schema.
Open Interest
One row per tickers.<symbol> message that carries openInterest or openInterestValue, plus one carried-forward row at the start of every hour.
- WebSocket tickers.<symbol>
Ticker snapshot
Bybit sends one snapshot with every field when the subscription starts. It publishes a ticker, a mark price and an open interest row. In our captures the first snapshot was up to 2 seconds old on arrival, and the deltas since then followed at once.
{"topic": "tickers.BTCUSDT","type": "snapshot","data": {"symbol": "BTCUSDT",→ symbol"tickDirection": "ZeroPlusTick","price24hPcnt": "0.004251","lastPrice": "85721.40","prevPrice24h": "85358.50","highPrice24h": "86986.50","lowPrice24h": "84907.70","prevPrice1h": "85436.60","markPrice": "85730.36","indexPrice": "85768.04","openInterest": "57163.259",→ sum_open_interest"openInterestValue": "4900626772.84",→ sum_open_interest_value"turnover24h": "5739447395.0800","volume24h": "66763.0850","fundingIntervalHour": "8","fundingCap": "0.00333","singleOpenInterest": "28581.63","singleOpenInterestValue": "2450313429.29","nextFundingTime": "1791244800000","fundingRate": "0.00001336","bid1Price": "85721.30","bid1Size": "2.542","ask1Price": "85721.40","ask1Size": "10.152","preOpenPrice": "","preQty": "","curPreListingPhase": ""},"cs": 820929695495,"ts": 1791227990283→ timestamp}
Published row
openInterest; Bybit's one-sided singleOpenInterest is not published.ts. Milliseconds.Ticker delta with a new open interest value
Any delta with openInterest or openInterestValue publishes an open interest row from the merged state. Here only the value moved (with the mark price), so sum_open_interest is carried forward. Replayed after the snapshot and the 23 deltas Bybit sent between them; those prepare the state and their rows are not shown.
Replayed after 24 earlier messages (not shown) that set the state this message builds on.
{"topic": "tickers.BTCUSDT","type": "delta","data": {"symbol": "BTCUSDT",→ symbol"tickDirection": "ZeroPlusTick","markPrice": "85730.18","openInterestValue": "4900616483.46",→ sum_open_interest_value"turnover24h": "5739473711.5495","volume24h": "66763.3920","ask1Price": "85721.40","ask1Size": "0.668","singleOpenInterestValue": "2450308284.59"},"cs": 820929710650,"ts": 1791227993083→ timestamp}
Published row
ts. Milliseconds.Hour-start carried-forward row
Every hour, each symbol with a known open interest gets a row at the hour start holding the state at the boundary. This snapshot was stamped just before 20:00 UTC and arrived just after, so it lands in the 20:00 file: it publishes its own row (stamped 19:59) and is folded into the hour-start row.
{"topic": "tickers.BTCUSDT","type": "snapshot","data": {"symbol": "BTCUSDT",→ symbol"tickDirection": "PlusTick","price24hPcnt": "0.003552","lastPrice": "85688.40","prevPrice24h": "85385.10","highPrice24h": "86986.50","lowPrice24h": "84907.70","prevPrice1h": "85604.10","markPrice": "85684.30","indexPrice": "85720.48","openInterest": "57257.185",→ sum_open_interest"openInterestValue": "4906041816.70",→ sum_open_interest_value"turnover24h": "5713818688.2134","volume24h": "66461.5850","fundingIntervalHour": "8","fundingCap": "0.00333","singleOpenInterest": "28628.593","singleOpenInterestValue": "2453020951.19","nextFundingTime": "1791244800000","fundingRate": "0.00002009","bid1Price": "85688.40","bid1Size": "5.141","ask1Price": "85688.50","ask1Size": "0.261","preOpenPrice": "","preQty": "","curPreListingPhase": ""},"cs": 820941027667,"ts": 1791230398283→ timestamp}
Published rows · 2
openInterest; Bybit's one-sided singleOpenInterest is not published.ts for the message's own row, which falls before the hour. The hour start, 1791230400000 (20:00:00 UTC), for the carried-forward row.Notes
- Bybit sends one
snapshotand thendeltamessages that hold only the fields that changed. The pipeline folds every message into the symbol's full state and publishes the merged state, so unchanged fields are filled from earlier messages. Asnapshotreplaces the state. - The state at the end of an hour carries into the next hour. When the previous hour's state is not available (first run, lost state, or an hour without
tickersmessages), an hour holds only the fields seen in it until the next snapshot. Files before the 2026-09 data-integrity release hold change-only rows with nulls for unchanged fields. - Bybit documents
openInterestandopenInterestValueas both sides of the market. The single-side figuressingleOpenInterestandsingleOpenInterestValueare not published. - In every hour with
tickersmessages, each symbol with a known open interest gets a row at the hour start withtimestamp= the hour start. Itsreceived_timeis the hour start, or later when a message stamped before the hour arrived after it (see the example). Before the October 2026 pipeline update this row always hadreceived_time= the hour start and left out such late messages. - Output of the REST open interest poller (
GET /v5/market/open-interest) is not published.
Column types and descriptions: Open Interest schema.
Liquidations
One row per entry of an allLiquidation.<symbol> message.
- WebSocket allLiquidation.<symbol>
S is the side of the liquidated position. Sell means a short was liquidated, so the published liquidation order side is BUY.
{"topic": "allLiquidation.BTCUSDT","type": "snapshot","ts": 1791227636482,→ event_time"data": [{"T": 1791227636066,→ trade_time"s": "BTCUSDT",→ symbol"S": "Sell",→ side"v": "1.312",→ quantity · last_filled_quantity · filled_quantity"p": "85914.10"→ price · average_price}]}
Published row
ts, when Bybit generated the push. Milliseconds.Buy (a long was liquidated) becomes SELL, Sell becomes BUY.Market placeholder. Bybit sends no order type.IOC placeholder. Bybit sends no time in force.FILLED placeholder. Bybit sends no order status.v, same as quantity.v, same as quantity.Notes
priceandaverage_priceboth hold the bankruptcy price.order_type,time_in_forceandorder_statusare fixed placeholders, andfilled_quantityandlast_filled_quantityequalquantity.datais an array; each entry becomes its own row. Rows are sorted bytrade_time. Bybit documents a push frequency of 500 ms for this topic and states that it pushes all liquidations.
Column types and descriptions: Liquidations schema.