Exchange Wire Formats

OKX Futures wire format

How OKX perpetual swaps (SWAP) 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 OKX perpetual swaps (SWAP) 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.

Every channel is subscribed per instrument with {"channel": …, "instId": "BTC-USDT-SWAP"}, except liquidations, which use one {"channel": "liquidation-orders", "instType": "SWAP"} subscription for all swaps. Sizes in trades, order book levels, the ticker's last_quantity, open interest and liquidations are in contracts. Multiply by the instrument's ctVal (OKX GET /api/v5/public/instruments), which is in ctValCcy units: the base coin for linear swaps (0.01 BTC for BTC-USDT-SWAP), USD for inverse swaps (100 USD for BTC-USD-SWAP).

  • Public WebSocket (books, tickers, mark price, funding rate, open interest, liquidations): wss://ws.okx.com:8443/ws/v5/public
  • Business WebSocket (trades-all): wss://ws.okx.com:8443/ws/v5/business

Exchange API reference: www.okx.com. Conventions shared by every exchange are on the wire formats overview.

Trades

One row per fill from the trades-all channel.

  • WebSocket trades-all

Every trades-all message carries exactly one fill.

WebSocket message · trades-allreceived 2026-10-05 19:15:00.442818 UTC
{
"arg": {
"channel": "trades-all",
"instId": "BTC-USDT-SWAP"
},
"data": [
{
"instId": "BTC-USDT-SWAP",
"tradeId": "2975282594",
"px": "85620.7",
"sz": "0.01",
"side": "sell",
"ts": "1791227700310",
"source": "0"
}
]
}

Published row

received_time1791227700442818000—Our collector's clock when the message arrived, in nanoseconds since the Unix epoch. Not sent by OKX.
event_time1791227700310data[].tsCopied from the fill time. Milliseconds.
symbolBTC-USDT-SWAPdata[].instIdCopied.
trade_id2975282594data[].tradeIdParsed from OKX's string to an integer.
price85620.7data[].pxCopied as the exact decimal string OKX sent.
quantity0.01data[].szCopied as the exact decimal string. Contracts: multiply by ctVal (0.01 BTC for BTC-USDT-SWAP) for the base coin.
trade_time1791227700310data[].tsCopied. Milliseconds. Same value as event_time.
is_buyer_makertruedata[].sidetrue when side is sell (the taker sold, so the buyer was the maker); false when side is buy.
order_typeMARKET—Always MARKET. A placeholder: OKX does not send an order type.

Notes

  • Rows come from the per-fill trades-all channel on the business endpoint. The public trades channel sends one entry per taker order, fill price and order source; we stopped using it in the 2026-09 data-integrity release (see the venue notes).
  • Rows in a file are sorted by trade_time, then trade_id. A trade_id is published once per hour file.
  • OKX's source field (0 normal order, 1 Enhanced Liquidity Program order) is not published.

Column types and descriptions: Trades schema.

Order Book

One row per price level from the 400-level books channel: a snapshot on subscribe, then 100 ms updates.

  • WebSocket books

Order book snapshot (action: snapshot)

OKX sends a 400-level snapshot right after subscribing. It anchors the update chain: its prevSeqId is always -1.

Abridged: OKX sent 400 bid and 400 ask levels in this message; the best three of each are shown.

WebSocket message · booksreceived 2026-10-05 19:15:00.148808 UTC
{
"arg": {
"channel": "books",
"instId": "BTC-USDT-SWAP"
},
"action": "snapshot",
"data": [
{
"asks": [
["85620.8", "1969.32", "0", "91"],
["85620.9", "52.78", "0", "4"],
["85621", "0.02", "0", "2"]
],
"bids": [
["85620.7", "741.39", "0", "21"],
["85620.6", "26.86", "0", "3"],
["85620.5", "0.64", "0", "2"]
],
"ts": "1791227700007",
"checksum": 0,
"seqId": 344108675409,
"prevSeqId": -1
}
]
}

Published rows · 6

received_time1791227700148808000—Our collector's clock when the message arrived, in nanoseconds since the Unix epoch. Not sent by OKX.
event_time1791227700007data[].tsCopied from the book generation time. Milliseconds.
transaction_timenull—Null. OKX sends one book timestamp only.
symbolBTC-USDT-SWAParg.instIdCopied.
event_typesnapshotactionsnapshot when action is snapshot.
first_update_idnull—Null. OKX sends no first update ID.
final_update_id344108675409data[].seqIdCopied from seqId.
prev_final_update_idnull—Null for snapshots. A snapshot starts a new chain.
last_update_id-1data[].prevSeqIdCopied from prevSeqId, which is always -1 for a snapshot. Kept for files written before prev_final_update_id existed.
sidebiddata[].bidsdata[].asksbid for levels in bids, ask for levels in asks. One row per level.
price85620.7data[].bids[][0]data[].asks[][0]Copied as the exact decimal string.
quantity741.39data[].bids[][1]data[].asks[][1]Copied as the exact decimal string. Contracts: multiply by ctVal (0.01 BTC for BTC-USDT-SWAP) for the base coin.
order_count21data[].bids[][3]data[].asks[][3]Copied from the fourth level element (number of orders), as an integer. The third element is deprecated (always 0) and not published.

Order book update (action: update)

Incremental changes, pushed every 100 ms while the book changes. Each update's prevSeqId equals the previous message's seqId.

Abridged: OKX sent 51 bid and 17 ask levels in this message; two bids and two asks are shown, including one level removed with quantity 0.

WebSocket message · booksreceived 2026-10-05 19:15:00.246680 UTC
{
"arg": {
"channel": "books",
"instId": "BTC-USDT-SWAP"
},
"action": "update",
"data": [
{
"asks": [
["85620.9", "52.19", "0", "3"],
["85623.7", "0", "0", "0"]
],
"bids": [
["85620.7", "771.51", "0", "23"],
["85619.7", "0.8", "0", "3"]
],
"ts": "1791227700107",
"checksum": 0,
"seqId": 344108675590,
"prevSeqId": 344108675409
}
]
}

Published rows · 4

received_time1791227700246680000—Our collector's clock when the message arrived, in nanoseconds since the Unix epoch. Not sent by OKX.
event_time1791227700107data[].tsCopied from the book generation time. Milliseconds.
transaction_timenull—Null. OKX sends one book timestamp only.
symbolBTC-USDT-SWAParg.instIdCopied.
event_typeupdateactionupdate when action is update.
first_update_idnull—Null. OKX sends no first update ID.
final_update_id344108675590data[].seqIdCopied from seqId.
prev_final_update_id344108675409data[].prevSeqIdCopied from prevSeqId. Equals the previous message's final_update_id; a mismatch is a gap.
last_update_id344108675409data[].prevSeqIdCopied from prevSeqId, the same value as prev_final_update_id. Kept for files written before prev_final_update_id existed.
sidebiddata[].bidsdata[].asksbid for levels in bids, ask for levels in asks. One row per level.
price85620.7data[].bids[][0]data[].asks[][0]Copied as the exact decimal string.
quantity771.51data[].bids[][1]data[].asks[][1]Copied as the exact decimal string: the new total at that price. 0 removes the level. Contracts: multiply by ctVal (0.01 BTC for BTC-USDT-SWAP) for the base coin.
order_count23data[].bids[][3]data[].asks[][3]Copied from the fourth level element (number of orders), as an integer.

Notes

  • OKX retired the books checksum in 2026: the field is still sent but is always 0 and is not published. seqId/prevSeqId is the only continuity check: an update continues the book when its prev_final_update_id equals the previous message's final_update_id. All rows of one message carry the same two IDs, so check once per message, not per row.
  • OKX documents that seqId can restart at a lower value after maintenance. prevSeqId still equals the previous message's seqId, so check the link, not that seqId increases.
  • The collector validates the chain before it writes. After a gap it reconnects and starts again from a new WebSocket snapshot.
  • Quantities are in contracts. Multiply by ctVal (0.01 BTC for BTC-USDT-SWAP) for the base coin.
  • When a book has not changed for about 60 seconds, OKX sends an update with no levels and seqId equal to prevSeqId. It is not published. The OKX Spot page shows an example.
  • Files can start with a checkpoint snapshot of the book carried over from the previous hour: its received_time is the hour start, transaction_time is null, and final_update_id and last_update_id are both the last seqId applied.

Column types and descriptions: Order Book schema.

Ticker

One row per tickers message.

  • WebSocket tickers
WebSocket message · tickersreceived 2026-10-05 19:15:00.038857 UTC
{
"arg": {
"channel": "tickers",
"instId": "BTC-USDT-SWAP"
},
"data": [
{
"instType": "SWAP",
"instId": "BTC-USDT-SWAP",
"last": "85620.7",
"lastSz": "0.08",
"askPx": "85620.8",
"askSz": "1989.68",
"bidPx": "85620.7",
"bidSz": "664.09",
"open24h": "85350.7",
"high24h": "86963.7",
"low24h": "84937.5",
"sodUtc0": "86484.8",
"sodUtc8": "85221.6",
"volCcy24h": "83846.1822",
"vol24h": "8384618.22",
"ts": "1791227699670"
}
]
}

Published row

received_time1791227700038857000—Our collector's clock when the message arrived, in nanoseconds since the Unix epoch. Not sent by OKX.
event_time1791227699670data[].tsCopied from the ticker generation time. Milliseconds.
symbolBTC-USDT-SWAPdata[].instIdCopied.
price_change270.0data[].lastdata[].open24hComputed: last − open24h, exact decimal.
price_change_percent0.31634187data[].lastdata[].open24hComputed: (last − open24h) / open24h × 100, rounded half-even to 8 decimal places. Percent.
weighted_average_pricenull—Null. OKX does not send a weighted average price.
last_price85620.7data[].lastCopied.
last_quantity0.08data[].lastSzCopied. Contracts.
open_price85350.7data[].open24hCopied. Open price of the past 24 hours.
high_price86963.7data[].high24hCopied.
low_price84937.5data[].low24hCopied.
base_asset_volume83846.1822data[].volCcy24hCopied from volCcy24h: for swaps OKX gives it in the base coin (BTC), for linear and inverse swaps alike.
quote_asset_volumenull—Null for swaps. OKX sends no 24h quote turnover for swaps (vol24h counts contracts and is not published).
statistics_open_timenull—Null. OKX does not send the window bounds.
statistics_close_timenull—Null. OKX does not send the window bounds.
first_trade_idnull—Null. OKX does not send trade IDs in tickers.
last_trade_idnull—Null. OKX does not send trade IDs in tickers.
total_tradesnull—Null. OKX does not send a trade count.

Notes

  • For swaps base_asset_volume is the 24h volume in the base coin (volCcy24h) and quote_asset_volume is null. OKX sends no quote turnover for swaps.
  • Best bid and ask (bidPx, bidSz, askPx, askSz) and the day opens sodUtc0 and sodUtc8 are not published.
  • Before the October 2026 pipeline update, quote_asset_volume for swaps held volCcy24h × last. Before the 2026-09 data-integrity release, base_asset_volume held contracts and quote_asset_volume the base-coin volume (see the venue notes).

Column types and descriptions: Ticker schema.

Mark Price

Two channels write into one file: mark-price rows carry mark_price, funding-rate rows carry funding_rate and next_funding_time. Rows are sorted by event_time.

  • WebSocket mark-price
  • WebSocket funding-rate

Mark price (mark-price channel)

Pushed every 200 ms while the mark price changes, otherwise every 10 seconds. These rows carry the mark price only.

WebSocket messagereceived 2026-10-05 19:14:59.991433 UTC
{
"arg": {
"channel": "mark-price",
"instId": "BTC-USDT-SWAP"
},
"data": [
{
"instId": "BTC-USDT-SWAP",
"instType": "SWAP",
"markPx": "85623.5",
"ts": "1791227699708"
}
]
}

Published row

received_time1791227699991433000—Our collector's clock when the message arrived, in nanoseconds since the Unix epoch. Not sent by OKX.
event_time1791227699708data[].tsCopied from the price update time. Milliseconds.
symbolBTC-USDT-SWAPdata[].instIdCopied.
mark_price85623.5data[].markPxCopied.
index_pricenull—Null. The mark-price channel has no index price.
estimated_settle_pricenull—Null. Not sent on this channel.
funding_ratenull—Null on mark price rows. Funding comes in separate rows from the funding-rate channel.
next_funding_timenull—Null on mark price rows.

Funding rate (funding-rate channel)

Pushed every 30 to 90 seconds. These rows carry the funding fields only and go into the same file as the mark price rows.

WebSocket messagereceived 2026-10-05 19:16:10.086326 UTC
{
"arg": {
"channel": "funding-rate",
"instId": "BTC-USDT-SWAP"
},
"data": [
{
"formulaType": "withRate",
"fundingRate": "0.0000263029004785",
"fundingTime": "1791244800000",
"impactValue": "20000.0000000000000000",
"instId": "BTC-USDT-SWAP",
"instType": "SWAP",
"interestRate": "0.0001000000000000",
"maxFundingRate": "0.00375",
"method": "current_period",
"minFundingRate": "-0.00375",
"nextFundingRate": "",
"nextFundingTime": "1791273600000",
"premium": "-0.0005569827183559",
"prevFundingTime": "1791216000000",
"settFundingRate": "0.0000265340358766",
"settState": "settled",
"ts": "1791227769932"
}
]
}

Published row

received_time1791227770086326000—Our collector's clock when the message arrived, in nanoseconds since the Unix epoch. Not sent by OKX.
event_time1791227769932data[].tsCopied from ts, the time OKX published the update. Milliseconds. If ts is missing, our receive time in milliseconds.
symbolBTC-USDT-SWAPdata[].instIdCopied.
mark_pricenull—Null on funding rows. Mark prices come in separate rows from the mark-price channel.
index_pricenull—Null. Not sent on this channel.
estimated_settle_pricenull—Null. Not sent on this channel.
funding_rate0.0000263029004785data[].fundingRateCopied. The current funding rate, a fraction per funding period: 0.0000263029004785 is 0.00263 %.
next_funding_time1791244800000data[].fundingTimeCopied from fundingTime: the upcoming settlement that fundingRate applies to. Milliseconds. Not nextFundingTime, which is the settlement after it.

Notes

  • Each row has either the mark price or the funding fields; the others are null. Forward-fill per column to get both on one row.
  • index_price and estimated_settle_price are always null for OKX.
  • Funding rows published before the 2026-09 data-integrity release have event_time set to fundingTime (see the venue notes).
  • OKX states that during maintenance two mark-price messages can share a ts, and that the later one received is authoritative. Both are published.

Column types and descriptions: Mark Price schema.

Open Interest

One row per open-interest message, about every 3 seconds while open interest changes.

  • WebSocket open-interest

Pushed every 3 seconds when open interest changes.

WebSocket message · open-interestreceived 2026-10-05 19:15:12.109227 UTC
{
"arg": {
"channel": "open-interest",
"instId": "BTC-USDT-SWAP"
},
"data": [
{
"instId": "BTC-USDT-SWAP",
"instType": "SWAP",
"oi": "2850839.22000000697",
"oiCcy": "28508.3922000000697",
"oiUsd": "2440908496.03854596776279",
"ts": "1791227711966"
}
]
}

Published row

received_time1791227712109227000—Our collector's clock when the message arrived, in nanoseconds since the Unix epoch. Not sent by OKX.
symbolBTC-USDT-SWAPdata[].instIdCopied.
sum_open_interest2850839.22000000697data[].oiCopied from oi. Contracts: multiply by ctVal (0.01 BTC for BTC-USDT-SWAP) for the base coin. OKX's base-coin figure oiCcy is not published.
sum_open_interest_value2440908496.03854596776279data[].oiUsdCopied from oiUsd. USD.
timestamp1791227711966data[].tsCopied from the update time. Milliseconds.

Notes

  • Messages with the same ts are published once, keeping the first one received.
  • The collector also has a REST open interest poller (GET /api/v5/public/open-interest); its output is not published.

Column types and descriptions: Open Interest schema.

Liquidations

One row per entry in details of the liquidation-orders channel, subscribed for all swaps.

  • WebSocket liquidation-orders
WebSocket message · liquidation-ordersreceived 2026-10-05 19:18:19.224997 UTC
{
"arg": {
"channel": "liquidation-orders",
"instType": "SWAP"
},
"data": [
{
"details": [
{
"bkLoss": "0",
"bkPx": "85659.7",
"ccy": "",
"posSide": "short",
"side": "buy",
"sz": "0.06",
"ts": "1791227898140"
}
],
"instFamily": "BTC-USDT",
"instId": "BTC-USDT-SWAP",
"instType": "SWAP",
"uly": "BTC-USDT"
}
]
}

Published row

received_time1791227899224997000—Our collector's clock when the message arrived, in nanoseconds since the Unix epoch. Not sent by OKX.
event_time1791227898140data[].details[].tsCopied from the liquidation time. Milliseconds.
symbolBTC-USDT-SWAPdata[].instIdCopied from the entry's instId. The subscription covers every swap, so one frame can carry several instruments.
sideBUYdata[].details[].sideUppercased: the side of the liquidation order. BUY closes a liquidated short, SELL a liquidated long.
order_typeMARKET—Always MARKET. A placeholder: OKX does not send an order type.
time_in_forceIOC—Always IOC. A placeholder: OKX does not send a time in force.
quantity0.06data[].details[].szCopied. Contracts: multiply by ctVal for the base coin.
price85659.7data[].details[].bkPxCopied from bkPx. OKX documents it as the liquidation mark price: the price of the transaction with the system's liquidation account. Not a fill price on the order book.
average_price85659.7data[].details[].bkPxCopied from bkPx, the same value as price. Not an execution price.
order_statusFILLED—Always FILLED. A placeholder, not an execution status.
last_filled_quantity0.06data[].details[].szCopied from sz, the same value as quantity.
filled_quantity0.06data[].details[].szCopied from sz, the same value as quantity.
trade_time1791227898140data[].details[].tsCopied. Milliseconds. Same value as event_time.

Notes

  • OKX states that this channel does not carry every liquidation on OKX.
  • OKX states that liquidations come from different sources and are not necessarily in time order. They can arrive hours after event_time, so a row can be in a later hour's file (see the venue notes).
  • posSide, bkLoss and ccy are not published.

Column types and descriptions: Liquidations schema.