8000 feat(binance): add eOptions WebSocket support by lucasjamar · Pull Request #27982 · ccxt/ccxt · GitHub
[go: up one dir, main page]

Skip to content

feat(binance): add eOptions WebSocket support#27982

Open
lucasjamar wants to merge 18 commits intoccxt:masterfrom
lucasjamar:binance-eoptions-ws
Open

feat(binance): add eOptions WebSocket support#27982
lucasjamar wants to merge 18 commits intoccxt:masterfrom
lucasjamar:binance-eoptions-ws

Conversation

@lucasjamar
Copy link
Contributor

Add full public + private WebSocket support for Binance eOptions (European-style options, eapi.binance.com):
fix: #26333

Public streams:

  • watchOrderBookForSymbols, unWatchOrderBookForSymbols
  • watchTradesForSymbols, unWatchTradesForSymbols (uses @optiontrade stream)
  • watchOHLCVForSymbols, unWatchOHLCVForSymbols
  • watchTickers, watchBidsAsks, watchMarkPrices (via watchMultiTickerHelper)
  • unWatchBidsAsks (new method)
  • Mark price stream: @optionMarkPrice (per-underlying, deduped)
  • watchLiquidationsForSymbols: throws NotSupported (no eOptions liquidation stream)

Private streams:

  • watchBalance, watchOrders, watchMyTrades, watchPositions
  • handleOptionsAccountUpdate for BALANCE_POSITION_UPDATE events
  • parseWsOptionsPosition for eOptions P[] position entries
  • authenticate/keepAliveListenKey via eapiPrivate{Post,Put}ListenKey

Fixes:

  • parseWsTrade, parseWsOrder, handleTrade, handleOHLCV: detect option symbols by '-' in market ID
  • parseWsTicker: use safeString2(mp, p) for mark price to support eOptions mp field
  • getAccountTypeFromSubscriptions: add 'option' account type

lucasjamar and others added 13 commits February 24, 2026 17:15
Add full public + private WebSocket support for Binance eOptions (European-style options, eapi.binance.com):

Public streams:
- watchOrderBookForSymbols, unWatchOrderBookForSymbols
- watchTradesForSymbols, unWatchTradesForSymbols (uses @optiontrade stream)
- watchOHLCVForSymbols, unWatchOHLCVForSymbols
- watchTickers, watchBidsAsks, watchMarkPrices (via watchMultiTickerHelper)
- unWatchBidsAsks (new method)
- Mark price stream: <underlying>@optionMarkPrice (per-underlying, deduped)
- watchLiquidationsForSymbols: throws NotSupported (no eOptions liquidation stream)

Private streams:
- watchBalance, watchOrders, watchMyTrades, watchPositions
- handleOptionsAccountUpdate for BALANCE_POSITION_UPDATE events
- parseWsOptionsPosition for eOptions P[] position entries
- authenticate/keepAliveListenKey via eapiPrivate{Post,Put}ListenKey

Fixes:
- parseWsTrade, parseWsOrder, handleTrade, handleOHLCV: detect option symbols by '-' in market ID
- parseWsTicker: use safeString2(mp, p) for mark price to support eOptions mp field
- getAccountTypeFromSubscriptions: add 'option' account type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
new Set<string>() does not transpile correctly to C#, PHP, Go, or Python.
Replace with a plain object used as a set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…eoptions/ws

Was incorrectly using fstream.binance.com (futures URL). The eOptions
public and private WS streams both use wss://nbstream.binance.com/eoptions/ws.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use wss://fstream.binance.com/public/stream for tickers/depth/trades
- Use wss://fstream.binance.com/market/stream for mark prices and klines
- Unwrap {"stream": "...", "data": {...}} combined stream message format
  in handleMessage so events are routed correctly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Binance's isLinear() override returns subType == 'linear' regardless of
marketType. Since USDT-settled eOptions have linear=True, handleSubTypeAndParams
sets subType='linear', causing isLinear() to return True for option markets.
This routed option symbols to the futures WebSocket URL instead of the
eOptions endpoint.

Fix: move the `marketType === 'option'` check before the isLinear check in
watchMultiTickerHelper so options always route to the correct endpoint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CCXT uses subscribe/unsubscribe JSON messages over a persistent WebSocket
connection, appending an index (e.g. /0) to the base URL. The combined
stream URLs (/public/stream, /market/stream) only support query-param
subscriptions (?streams=...) and return 404 when accessed as /stream/0.

Correct base URLs for eOptions WebSocket subscribe/unsubscribe API:
  /public/ws  (depth, trades, tickers)
  /market/ws  (mark price, klines, OI)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- watchTradesForSymbols: remove @optiontrade override — Binance eOptions
  uses the standard @trade stream (not @optiontrade). The handleTrade
  handler already detects option markets via the '-' in the market ID.

- watchOHLCVForSymbols / unWatchOHLCVForSymbols: route option markets
  to 'optionMarket' URL (/market/ws) instead of 'option' (/public/ws),
  since eOptions kline streams are served from the /market/ws endpoint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The user data (private) stream for Binance eOptions connects to:
  wss://fstream.binance.com/private/ws/<listenKey>

Not /public/ws. See:
https://developers.binance.com/docs/derivatives/options-trading/user-data-streams

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…erBook

- watchOHLCVForSymbols / unWatchOHLCVForSymbols: introduce separate
  wsUrlType variable (type: Str) to hold the WS URL routing key
  ('optionMarket') without conflicting with the MarketType-typed 'type'
  variable. Fixes TS2322 error.

- handleOrderBook: add isOption detection (same pattern as handleTrade)
  so option depth update messages use marketType='option' for market
  lookup instead of 'swap', ensuring correct symbol resolution.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace indexOf('-') heuristic with markets_by_id lookup for option detection
- Consolidate isSpotUrl + 'ps' field checks into marketById['type'] lookup
- watchMarkPrices: fix defaultMarket overriding defaultType='option', fix isOptionMarkPrice detection when no symbols given, wait on batch hash so full dict is returned when symbols list provided
- watchMarkPrices/watchTickers: support underlying+expirationDate params when no symbols given
- watchTradesForSymbols: always subscribe per-underlying (btcusdt@optionTrade) for eOptions
- watchMultiTickerHelper: return batch result directly for isOptionMarkPrice (fixes KeyError 'symbol')
- fetchMarkPrice/fetchMarkPrices: add eOptions branch via eapiPublicGetMark
- fetchBidsAsks: add eOptions branch via eapiPublicGetTicker

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- handleOptionsOrderUpdate: handle eOptions ORDER_TRADE_UPDATE where "o"
  is an array of orders (not a dict like futures), each with a "fi" fills
  array. Normalizes eOptions fields (oid→i, S status→X, oty→o, tif→f,
  signed q→abs qty + side) to the flat format handleOrder/handleMyTrade
  already understand. Dispatches per-fill trades for watchMyTrades.
- handleBalance: default safeList(message, 'B') to [] to prevent
  TypeError when ACCOUNT_UPDATE arrives without a B field on eOptions stream

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace ternary expressions with if/else blocks to avoid Python syntax
errors generated by the transpiler for conditions with method calls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lucasjamar
Copy link
Contributor Author
lucasjamar commented Feb 27, 2026

Hi CCXT Team,
Ive added compatibility with binance options websockets. implemented methods:
public:

  • watch_trades(_for_symbols)
  • watch_order_book(_for_symbols)
  • watch_bids_asks
  • watch_ticker(s)
  • watch_mark_price(s)
  • watch_ohlcv(_for_symbols)
  • fetch_bids_asks
    private:
  • watch_orders
  • watch_my_trades (inferred from orders)
  • watch_positions
  • watch_balance

Local testing on all endpoints worked.

Can you please have a look?

lucasjamar and others added 2 commits February 27, 2026 12:03
Replace rawQty.charAt(0) string manipulation (JS-only) with
safeNumber + Math.abs + numberToString which transpiles correctly
to all target languages including C#.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace Math.abs (transpiles to undefined mathAbs in Go) with
Precise.stringLt + Precise.stringAbs which transpile correctly
to all target languages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lucasjamar lucasjamar marked this pull request as ready for review February 27, 2026 12:27
ccxt.Remove() panics in Go when the key does not exist (unlike JS delete
which is a safe no-op). The handleOrderBook catch block deletes
this.orderbooks[symbol] and client.subscriptions[messageHash], both of
which may be absent when a ChecksumError occurs. Add 'in' checks before
each delete so the transpiled Go uses ccxt.InOp guards before Remove.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@carlosmiei
Copy link
Collaborator

Hello @lucasjamar thanks for your contribution, cna you please revert all files but .ts? All the others are automatically generated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lucasjamar
Copy link
Contributor Author

Hello @lucasjamar thanks for your contribution, cna you please revert all files but .ts? All the others are automatically generated.

done

@Dan-krm
Copy link
Contributor
Dan-krm commented Mar 5, 2026

@lucasjamar I think there's some issues with options testnet/demo currently so it might be best to remove those URL's for now.

When I sign on to the demo site there's no option contracts available.

The REST API would need to be adjusted when the demo account is working for options, the test and demo URLs for options were removed in the REST implementation and there's this skip in fetchMarkets:

if (type === 'option' && isDemoEnv) {
    continue;
}

There's also a skip for testnet.binancefuture.com in the sign method that was added recently, and it says to use that URL for the option REST API, so it seems like the "Testnet API Information" in the documentation for general info for options could be out of date currently.

…rivate methods

Option markets are not available in testnet/demo environments.
Matches the existing REST behavior where fetchMarkets skips option
markets when isDemoEnv is true.

- Remove option/optionMarket/optionPrivate URLs from test and demo WS URL blocks
- Add NotSupported guard in watchBalance/watchOrders/watchPositions/watchMyTrades
  when type='option' and sandbox/demo mode is enabled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lucasjamar
Copy link
Contributor Author

@lucasjamar I think there's some issues with options testnet/demo currently so it might be best to remove those URL's for now.

When I sign on to the demo site there's no option contracts available.

The REST API would need to be adjusted when the demo account is working for options, the test and demo URLs for options were removed in the REST implementation and there's this skip in fetchMarkets:

if (type === 'option' && isDemoEnv) {
    continue;
}

There's also a skip for testnet.binancefuture.com in the sign method that was added recently, and it says to use that URL for the option REST API, so it seems like the "Testnet API Information" in the documentation for general info for options could be out of date currently.

Hi @Dan-krm , is the current version ok? my tests still pass on my side

@Dan-krm
Copy link
Contributor
Dan-krm commented Mar 6, 2026

Hi @Dan-krm , is the current version ok? my tests still pass on my side

@lucasjamar Yeah that looks good to me, and we can watch for any demo/sandbox updates in the changelog and remove those checks later when it's properly supported.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Binance Optoins API stuck on watch_order_book_for_symbols, watch_trades_for_symbols

3 participants

0