Major, much-requested, changes have been made to our WebSockets for fine-grain control over specific event subscriptions:
- PING-PONG heartbeats to stay connected (every 10 seconds the server sends a PING and the client should respond within 15 seconds).
- We allow up to 20 client connections using the same account address (which is obtained through the JWT token provided for the subscription).
- Auth token generation is different and has expiry → You must get a fresh token within 5 minutes (the current expiration time) by calling the refresh token endpoint or with this function in the SDK.
- This token is used for authentication at the moment of subscription for Account Data streams in our WebSockets. If the connection is kept alive, then you won’t have to change it in order to keep receiving updates; however, if you get disconnected and have to establish a new connection, you need to pass in a new Auth token (provided the current one expired).
Account Data Streams
Listen to the Account Data
streams for account, trade, position, and order updates.
- Limited connections → 20 client-server connections per account (as explained above).
- Auth required → Need to refresh the JWT token if the connection is lost.
- Now you subscribe to individual streams (shown in the screenshots below) as opposed to rooms allowing you granular control of what you want to receive updates for.
Available Streams
Frequency | Stream Name | Event | |
---|---|---|---|
Account | Real Time | AccountUpdate | AccountUpdate |
Transactions | Real Time | AccountTransactionUpdate | AccountTransactionUpdate |
Positions | Real Time | AccountPositionUpdate | AccountPositionUpdate |
Trades | Real Time | AccountTradeUpdate | AccountTradeUpdate |
Orders | Real Time | AccountOrderUpdate | AccountOrderUpdate |
Note that the AccountOrderUpdate
is a special message, represented as an enum which can either be:
*ActiveOrderUpdate
: This is used to track the lifecycle of an active order, basically any status other than cancelled.**OrderCancellationUpdate
: This is used to alert a user of a cancelled or a rejected order. An order is considered as “cancelled” either if it has been successfully cancelled OR if it has been rejected by the engine, both of these scenarios will send anOrderCancellationUpdate
socket message.
How to Subscribe
- Choose the streams you want to individually subscribe to:
async def listen_bluefin_account():
async with BluefinProSdk(sui_wallet, network, debug=True) as client:
async with await client.create_account_data_stream_listener(
handler=handle_account_data_event
) as account_data_listener:
await account_data_listener.subscribe(
subscription=[
AccountDataStream.ACCOUNTORDERUPDATE,
AccountDataStream.ACCOUNTPOSITIONUPDATE,
AccountDataStream.ACCOUNTTRADEUPDATE,
AccountDataStream.ACCOUNTTRANSACTIONUPDATE,
AccountDataStream.ACCOUNTUPDATE
]
)
await asyncio.Event().wait()
- Handle the events:
# Handle account data stream events
async def handle_account_data_event(msg):
if isinstance(msg, AccountUpdate):
logger.info(f"AccountUpdate: {msg}")
if isinstance(msg, AccountTransactionUpdate):
logger.info(f"AccountTransactionUpdate: {msg}")
if isinstance(msg, AccountTradeUpdate):
logger.info(f"AccountTradeUpdate: {msg}")
if isinstance(msg, AccountPositionUpdate):
logger.info(f"AccountPositionUpdate: {msg}")
if isinstance(msg, AccountOrderUpdate):
if isinstance(msg.actual_instance, ActiveOrderUpdate):
logger.info(f"ActiveOrderUpdate: {msg.actual_instance}")
if isinstance(msg.actual_instance, OrderCancellationUpdate):
logger.info(f"OrderCancellationUpdate: {msg.actual_instance}")
order = Order(
client_order_id="123456",
type=OrderType.LIMIT,
symbol="ETH-PERP",
price_e9="500000000",
quantity_e9="100000000000",
side=OrderSide.LONG,
leverage_e9="100000000",
is_isolated=False,
expires_at_utc_millis=int(time.time() * 1000) + 60000
)
await create_order(order)
Market Data Streams
Listen to the Market Data
streams for updates on the orderbook, recent trades, candlesticks, tickers, etc.
- Unlimited connections.
- No auth required.
- Again, you can subscribe to each stream individually, allowing granular control of what you want to listen to.
Available Streams
Frequency | Stream Name | Event | |
---|---|---|---|
Diff Depth | 10ms, 200ms or 500ms depending on the stream | Diff_Depth_10_ms , Diff_Depth_200_ms , Diff_Depth_500_ms | OrderbookDepthUpdate |
Partial Depth | Real Time | Partial_Depth_5 , Partial_Depth_10 , Diff_Depth_20 | OrderbookPartialDepthUpdate |
Oracle Price | 1s | Oracle_Price | OraclePriceUpdate |
Mark Price | 1s | Mark_Price | MarkPriceUpdate |
Market Price | 1s | Market_Price | MarketPriceUpdate |
Recent Trades | 100ms | Recent_Trade | RecentTradesUpdate |
Tickers | 3s | Ticker , Ticker_All | TickerUpdate , TickerAllUpdates |
Candlesticks | 200ms | Candlestick_{interval}_{type} | CandlestickUpdate |
- Candlesticks
- Intervals:
1m
,3m
,5m
, … see OpenAPI spec for all enums - Type:
Last
,Oracle
,Market
- Example:
Candlestick_1m_Last
How to Subscribe
- Choose the streams you want to individually subscribe to:
async def listen_bluefin_market(market_symbol: str):
async with BluefinProSdk(sui_wallet, network, debug=True) as client:
async with await client.create_market_data_stream_listener(
handler=handle_market_data_event
) as market_data_stream:
await market_data_stream.subscribe(
subscription=[
MarketSubscriptionStreams.model_construct(
symbol=market_symbol,
streams=[
MarketDataStreamName.MARK_PRICE,
MarketDataStreamName.RECENT_TRADE,
MarketDataStreamName.ORACLE_PRICE,
MarketDataStreamName.TICKER,
MarketDataStreamName.TICKER_ALL,
MarketDataStreamName.DIFF_DEPTH_10_MS,
MarketDataStreamName.DIFF_DEPTH_200_MS,
MarketDataStreamName.DIFF_DEPTH_500_MS,
MarketDataStreamName.PARTIAL_DEPTH_5,
MarketDataStreamName.PARTIAL_DEPTH_5,
MarketDataStreamName.PARTIAL_DEPTH_10,
MarketDataStreamName.PARTIAL_DEPTH_20
]
)
]
)
await asyncio.Event().wait()
- Handle the events:
# Handle market data stream events
async def handle_market_data_event(msg):
if isinstance(msg, OraclePriceUpdate):
logger.info(f"OraclePriceUpdate: {msg}")
if isinstance(msg, MarkPriceUpdate):
logger.info(f"MarkPriceUpdate: {msg}")
if isinstance(msg, MarketPriceUpdate):
logger.info(f"MarketPriceUpdate: {msg}")
if isinstance(msg, CandlestickUpdate):
logger.info(f"CandlestickUpdate: {msg}")
if isinstance(msg, OrderbookDiffDepthUpdate):
logger.info(f"OrderbookDiffDepthUpdate: {msg}")
if isinstance(msg, OrderbookPartialDepthUpdate):
logger.info(f"OrderbookPartialDepthUpdate: {msg}")
if isinstance(msg, RecentTradesUpdates):
logger.info(f"RecentTradesUpdates: {msg}")
if isinstance(msg, TickerAllUpdate):
logger.info(f"TickerAllUpdate: {msg}")
if isinstance(msg, TickerUpdate):
logger.info(f"TickerUpdate: {msg}")
How to manage a local orderbook:
- Start by openning a stream and listening to the DiffDepth socket (
Diff_Depth_10_ms
,Diff_Depth_200_ms
,Diff_Depth_500_ms
) - Buffer the events you receive from the selected stream
- Get a depth snapshot from the
/exchange/depth
endpoint - Drop any event where
update.lastUpdateId
is <depthResponse.lastUpdateId
in the snapshot. - The first processed event should have
update.firestUpdateId
<=depthResponse.lastUpdateId
ANDupdate.lastUpdateId
>=depthResponse.lastUpdateId
- Note that you must ensure your apply the very first
orderbookUpdate
after theorderbookUpdateId
received from the snapshot (if you fail to do so, you risk missing updates yielding an inconsistent state). This is why it's recommended to open the socket connection first in step 1 before fetching the snapshot. - While listening to the stream, each new event's
firstUpdateId
should be equal to the previous event'slastUpdateId + 1
- Otherwise we must rest and initialize the process from step 3 again.
Additional Notes:
OrderbookUpdateId
will not be sequential since multiple updates are aggregated before being sent every n ms. It will however always be an increasing number.- If the returned quantity is 0 at any level, you should remove that price level.
- Receiving an event that removes a price level that is not in your local order book can happen and is normal