From 6b9447f6b45f0f62f1bf129a371569588f42496a Mon Sep 17 00:00:00 2001 From: Pablo Date: Tue, 18 Feb 2025 01:07:33 -0600 Subject: [PATCH 1/6] add timeout to jobs --- .github/workflows/python-app.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 2b0012d20..658baafff 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -16,6 +16,7 @@ permissions: jobs: lint: runs-on: ubuntu-latest + timeout-minutes: 5 steps: - uses: actions/checkout@v4 - name: Set up Python @@ -33,6 +34,7 @@ jobs: build: needs: lint runs-on: ubuntu-22.04 + timeout-minutes: 20 env: PROXY: "http://51.83.140.52:16301" TEST_TESTNET: "true" @@ -74,6 +76,7 @@ jobs: needs: build if: ${{ always() }} runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Coveralls Finished uses: coverallsapp/github-action@v2 From 66834927d0c5695b0d1715056d70fe4ea9435339 Mon Sep 17 00:00:00 2001 From: Pablo Date: Sun, 23 Feb 2025 22:37:30 -0600 Subject: [PATCH 2/6] feat: add ws support for options --- binance/ws/streams.py | 112 +++++++++++++++++++++------------- tests/test_streams_options.py | 75 +++++++++++++++++++++++ 2 files changed, 144 insertions(+), 43 deletions(-) create mode 100644 tests/test_streams_options.py diff --git a/binance/ws/streams.py b/binance/ws/streams.py index a135a3230..2b3b6b5c0 100755 --- a/binance/ws/streams.py +++ b/binance/ws/streams.py @@ -30,8 +30,7 @@ class BinanceSocketManager: FSTREAM_TESTNET_URL = "wss://stream.binancefuture.com/" DSTREAM_URL = "wss://dstream.binance.{}/" DSTREAM_TESTNET_URL = "wss://dstream.binancefuture.com/" - VSTREAM_URL = "wss://vstream.binance.{}/" - VSTREAM_TESTNET_URL = "wss://testnetws.binanceops.{}/" + OPTIONS_URL = "wss://nbstream.binance.{}/eoptions/" WEBSOCKET_DEPTH_5 = "5" WEBSOCKET_DEPTH_10 = "10" @@ -47,8 +46,7 @@ def __init__(self, client: AsyncClient, user_timeout=KEEPALIVE_TIMEOUT): self.STREAM_URL = self.STREAM_URL.format(client.tld) self.FSTREAM_URL = self.FSTREAM_URL.format(client.tld) self.DSTREAM_URL = self.DSTREAM_URL.format(client.tld) - self.VSTREAM_URL = self.VSTREAM_URL.format(client.tld) - self.VSTREAM_TESTNET_URL = self.VSTREAM_TESTNET_URL.format(client.tld) + self.OPTIONS_URL = self.OPTIONS_URL.format(client.tld) self._conns = {} self._loop = get_loop() @@ -129,14 +127,12 @@ def _get_futures_socket( return self._get_socket(path, stream_url, prefix, socket_type=socket_type) def _get_options_socket(self, path: str, prefix: str = "ws/"): - stream_url = self.VSTREAM_URL - if self.testnet: - stream_url = self.VSTREAM_TESTNET_URL + stream_url = self.OPTIONS_URL return self._get_socket( path, stream_url, prefix, - is_binary=True, + is_binary=False, socket_type=BinanceSocketType.OPTIONS, ) @@ -874,23 +870,11 @@ def multiplex_socket(self, streams: List[str]): def options_multiplex_socket(self, streams: List[str]): """Start a multiplexed socket using a list of socket names. - User stream sockets can not be included. - - Symbols in socket name must be lowercase i.e bnbbtc@aggTrade, neobtc@ticker - - Combined stream events are wrapped as follows: {"stream":"","data":} - - https://binance-docs.github.io/apidocs/voptions/en/#account-and-trading-interface - - :param streams: list of stream names in lower case - :type streams: list - - :returns: connection key string if successful, False otherwise - - Message Format - see Binance API docs for all types + + https://developers.binance.com/docs/derivatives/option/websocket-market-streams """ - stream_name = "/".join([s.lower() for s in streams]) + stream_name = "/".join([s for s in streams]) stream_path = f"streams={stream_name}" return self._get_options_socket(stream_path, prefix="stream?") @@ -1036,60 +1020,102 @@ def isolated_margin_socket(self, symbol: str): return self._get_account_socket(symbol, stream_url=stream_url) def options_ticker_socket(self, symbol: str): - """Subscribe to a 24 hour ticker info stream + """Subscribe to a 24-hour ticker info stream for options trading. - https://binance-docs.github.io/apidocs/voptions/en/#market-streams-payload-24-hour-ticker + API Reference: https://developers.binance.com/docs/derivatives/option/websocket-market-streams/24-hour-TICKER - :param symbol: required + Stream provides real-time 24hr ticker information for all symbols. Only symbols whose ticker info + changed will be sent. Updates every 1000ms. + + :param symbol: The option symbol to subscribe to (e.g. "BTC-220930-18000-C") :type symbol: str """ - return self._get_options_socket(symbol.lower() + "@ticker") + return self._get_options_socket(symbol.upper() + "@ticker") def options_ticker_by_expiration_socket(self, symbol: str, expiration_date: str): - """Subscribe to a 24 hour ticker info stream - https://binance-docs.github.io/apidocs/voptions/en/#24-hour-ticker-by-underlying-asset-and-expiration-data - :param symbol: required + """Subscribe to a 24-hour ticker info stream by underlying asset and expiration date. + + API Reference: https://developers.binance.com/docs/derivatives/option/websocket-market-streams/24-hour-TICKER-by-underlying-asset-and-expiration-data + + Stream provides real-time 24hr ticker information grouped by underlying asset and expiration date. + Updates every 1000ms. + + :param symbol: The underlying asset (e.g., "ETH") :type symbol: str - :param expiration_date : required + :param expiration_date: The expiration date (e.g., "220930" for Sept 30, 2022) :type expiration_date: str """ - return self._get_options_socket(symbol.lower() + "@ticker@" + expiration_date) + return self._get_options_socket(symbol.upper() + "@ticker@" + expiration_date) def options_recent_trades_socket(self, symbol: str): - """Subscribe to a latest completed trades stream + """Subscribe to a real-time trade information stream. + + API Reference: https://developers.binance.com/docs/derivatives/option/websocket-market-streams/Trade-Streams - https://binance-docs.github.io/apidocs/voptions/en/#market-streams-payload-latest-completed-trades + Stream pushes raw trade information for a specific symbol or underlying asset. + Updates every 50ms. - :param symbol: required + :param symbol: The option symbol or underlying asset (e.g., "BTC-200630-9000-P" or "BTC") :type symbol: str """ - return self._get_options_socket(symbol.lower() + "@trade") + return self._get_options_socket(symbol.upper() + "@trade") def options_kline_socket( self, symbol: str, interval=AsyncClient.KLINE_INTERVAL_1MINUTE ): - """Subscribe to a candlestick data stream + """Subscribe to a Kline/Candlestick data stream. + + API Reference: https://developers.binance.com/docs/derivatives/option/websocket-market-streams/Kline-Candlestick-Streams - https://binance-docs.github.io/apidocs/voptions/en/#market-streams-payload-candle + Stream pushes updates to the current klines/candlestick every 1000ms (if existing). - :param symbol: required + Available intervals: + - Minutes: "1m", "3m", "5m", "15m", "30m" + - Hours: "1h", "2h", "4h", "6h", "12h" + - Days: "1d", "3d" + - Weeks: "1w" + + :param symbol: The option symbol (e.g., "BTC-200630-9000-P") :type symbol: str :param interval: Kline interval, default KLINE_INTERVAL_1MINUTE :type interval: str """ - return self._get_options_socket(symbol.lower() + "@kline_" + interval) + return self._get_options_socket(symbol.upper() + "@kline_" + interval) def options_depth_socket(self, symbol: str, depth: str = "10"): - """Subscribe to a depth data stream + """Subscribe to partial book depth stream for options trading. + + API Reference: https://developers.binance.com/docs/derivatives/option/websocket-market-streams/Partial-Book-Depth-Streams - https://binance-docs.github.io/apidocs/voptions/en/#market-streams-payload-depth + Stream provides top N bids and asks from the order book. + Default update speed is 500ms if not specified in the stream name. + + :param symbol: The option symbol (e.g., "BTC-200630-9000-P") + :type symbol: str + :param depth: Number of price levels. Valid values: "10", "20", "50", "100" + :type depth: str + """ + return self._get_options_socket(symbol.upper() + "@depth" + str(depth)) + + def futures_depth_socket( + self, + symbol: str, + depth: str = "10", + futures_type=FuturesType.USD_M, + ): + """Subscribe to a futures depth data stream + + https://binance-docs.github.io/apidocs/futures/en/#partial-book-depth-streams :param symbol: required :type symbol: str :param depth: optional Number of depth entries to return, default 10. :type depth: str + :param futures_type: use USD-M or COIN-M futures default USD-M """ - return self._get_options_socket(symbol.lower() + "@depth" + str(depth)) + return self._get_futures_socket( + symbol.lower() + "@depth" + str(depth), futures_type=futures_type + ) async def _stop_socket(self, conn_key): """Stop a websocket given the connection key diff --git a/tests/test_streams_options.py b/tests/test_streams_options.py new file mode 100644 index 000000000..9dbcc97a8 --- /dev/null +++ b/tests/test_streams_options.py @@ -0,0 +1,75 @@ +import sys +import pytest +from binance import BinanceSocketManager +from binance.async_client import AsyncClient +from .conftest import proxy, api_key, api_secret, testnet + +pytestmark = [ + pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+"), + pytest.mark.asyncio +] + +# Test constants +OPTION_SYMBOL = "BTC-250328-40000-P" +UNDERLYING_SYMBOL = "BTC" +EXPIRATION_DATE = "250328" +INTERVAL = "1m" +DEPTH = "20" + +async def test_options_ticker(clientAsync): + """Test options ticker socket""" + bm = BinanceSocketManager(clientAsync) + socket = bm.options_ticker_socket(OPTION_SYMBOL) + async with socket as ts: + msg = await ts.recv() + assert msg['e'] == '24hrTicker' + await clientAsync.close_connection() + +async def test_options_ticker_by_expiration(clientAsync): + """Test options ticker by expiration socket""" + bm = BinanceSocketManager(clientAsync) + socket = bm.options_ticker_by_expiration_socket(UNDERLYING_SYMBOL, EXPIRATION_DATE) + async with socket as ts: + msg = await ts.recv() + assert len(msg) > 0 + await clientAsync.close_connection() + +async def test_options_recent_trades(clientAsync): + """Test options recent trades socket""" + bm = BinanceSocketManager(clientAsync) + socket = bm.options_recent_trades_socket(UNDERLYING_SYMBOL) + async with socket as ts: + msg = await ts.recv() + assert msg['e'] == 'trade' + await clientAsync.close_connection() + +async def test_options_kline(clientAsync): + """Test options kline socket""" + bm = BinanceSocketManager(clientAsync) + socket = bm.options_kline_socket(OPTION_SYMBOL, INTERVAL) + async with socket as ts: + msg = await ts.recv() + assert msg['e'] == 'kline' + await clientAsync.close_connection() + +async def test_options_depth(clientAsync): + """Test options depth socket""" + bm = BinanceSocketManager(clientAsync) + socket = bm.options_depth_socket(OPTION_SYMBOL, DEPTH) + async with socket as ts: + msg = await ts.recv() + assert msg['e'] == 'depth' + await clientAsync.close_connection() + +async def test_options_multiplex(clientAsync): + """Test options multiplex socket""" + bm = BinanceSocketManager(clientAsync) + streams = [ + f"{OPTION_SYMBOL}@ticker", + f"{OPTION_SYMBOL}@trade", + ] + socket = bm.options_multiplex_socket(streams) + async with socket as ts: + msg = await ts.recv() + assert 'stream' in msg + await clientAsync.close_connection() From 140931b34e81d124cc9160d093e0e38c17e788f4 Mon Sep 17 00:00:00 2001 From: Pablo Date: Sun, 23 Feb 2025 22:46:16 -0600 Subject: [PATCH 3/6] add missing functions --- binance/ws/streams.py | 82 +++++++++++++++++++++++++++++++++++ tests/test_streams_options.py | 27 ++++++++++++ 2 files changed, 109 insertions(+) diff --git a/binance/ws/streams.py b/binance/ws/streams.py index 2b3b6b5c0..c5909f392 100755 --- a/binance/ws/streams.py +++ b/binance/ws/streams.py @@ -1117,6 +1117,88 @@ def futures_depth_socket( symbol.lower() + "@depth" + str(depth), futures_type=futures_type ) + def options_new_symbol_socket(self): + """Subscribe to a new symbol listing information stream. + + Stream provides real-time notifications when new option symbols are listed. + Updates every 50ms. + + Stream name: option_pair + + API Reference: https://developers.binance.com/docs/derivatives/option/websocket-market-streams/New-Symbol-Info + + Response fields include: + - Event type and timestamps + - Underlying index (e.g., 'BTCUSDT') + - Quotation asset (e.g., 'USDT') + - Trading pair name (e.g., 'BTC-221116-21000-C') + - Conversion ratio and minimum trade volume + - Option type (CALL/PUT) + - Strike price and expiration time + """ + return self._get_options_socket("option_pair") + + def options_open_interest_socket(self, symbol: str, expiration_date: str): + """Subscribe to an options open interest stream. + + Stream provides open interest information for specific underlying asset on specific expiration date. + Updates every 60 seconds. + + Stream name format: @openInterest@ + + API Reference: https://developers.binance.com/docs/derivatives/option/websocket-market-streams/Open-Interest + + Response fields include: + - Event type and timestamps + - Option symbol (e.g., 'ETH-221125-2700-C') + - Open interest in contracts + - Open interest in USDT + + :param symbol: The underlying asset (e.g., "ETH") + :type symbol: str + :param expiration_date: The expiration date (e.g., "221125" for Nov 25, 2022) + :type expiration_date: str + """ + return self._get_options_socket(symbol.upper() + "@openInterest@" + expiration_date) + + def options_mark_price_socket(self, symbol: str): + """Subscribe to an options mark price stream. + + Stream provides mark price information for all option symbols on specific underlying asset. + Updates every 1000ms. + + Stream name format: @markPrice + + API Reference: https://developers.binance.com/docs/derivatives/option/websocket-market-streams/Mark-Price + + Response fields include: + - Event type and timestamps + - Option symbol (e.g., 'ETH-220930-1500-C') + - Option mark price + + :param symbol: The underlying asset (e.g., "ETH") + :type symbol: str + """ + return self._get_options_socket(symbol.upper() + "@markPrice") + + def options_index_price_socket(self, symbol: str): + """Subscribe to an options index price stream. + + API Reference: https://developers.binance.com/docs/derivatives/option/websocket-market-streams/Index-Price-Streams + + Stream provides index price information for underlying assets (e.g., ETHUSDT). + Updates every 1000ms. + + Response fields include: + - Event type and timestamps + - Underlying symbol (e.g., 'ETHUSDT') + - Index price + + :param symbol: The underlying symbol (e.g., "ETHUSDT") + :type symbol: str + """ + return self._get_options_socket(symbol.upper() + "@index") + async def _stop_socket(self, conn_key): """Stop a websocket given the connection key diff --git a/tests/test_streams_options.py b/tests/test_streams_options.py index 9dbcc97a8..1b2693e9e 100644 --- a/tests/test_streams_options.py +++ b/tests/test_streams_options.py @@ -73,3 +73,30 @@ async def test_options_multiplex(clientAsync): msg = await ts.recv() assert 'stream' in msg await clientAsync.close_connection() + +async def test_options_open_interest(clientAsync): + """Test options open interest socket""" + bm = BinanceSocketManager(clientAsync) + socket = bm.options_open_interest_socket(UNDERLYING_SYMBOL, EXPIRATION_DATE) + async with socket as ts: + msg = await ts.recv() + assert len(msg) > 0 + await clientAsync.close_connection() + +async def test_options_mark_price(clientAsync): + """Test options mark price socket""" + bm = BinanceSocketManager(clientAsync) + socket = bm.options_mark_price_socket(UNDERLYING_SYMBOL) + async with socket as ts: + msg = await ts.recv() + assert len(msg) > 0 + await clientAsync.close_connection() + +async def test_options_index_price(clientAsync): + """Test options index price socket""" + bm = BinanceSocketManager(clientAsync) + socket = bm.options_index_price_socket('ETHUSDT') + async with socket as ts: + msg = await ts.recv() + assert msg['e'] == 'index' + await clientAsync.close_connection() From 060d1e9f0a8a7b669bc84d046f4aeecf3a5a2fb2 Mon Sep 17 00:00:00 2001 From: Pablo Date: Sun, 23 Feb 2025 22:49:49 -0600 Subject: [PATCH 4/6] lint --- binance/ws/streams.py | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/binance/ws/streams.py b/binance/ws/streams.py index c5909f392..e2a550f19 100755 --- a/binance/ws/streams.py +++ b/binance/ws/streams.py @@ -655,23 +655,6 @@ def index_price_socket(self, symbol: str, fast: bool = True): symbol.lower() + stream_name, futures_type=FuturesType.COIN_M ) - def futures_depth_socket( - self, symbol: str, depth: str = "10", futures_type=FuturesType.USD_M - ): - """Subscribe to a futures depth data stream - - https://binance-docs.github.io/apidocs/futures/en/#partial-book-depth-streams - - :param symbol: required - :type symbol: str - :param depth: optional Number of depth entries to return, default 10. - :type depth: str - :param futures_type: use USD-M or COIN-M futures default USD-M - """ - return self._get_futures_socket( - symbol.lower() + "@depth" + str(depth), futures_type=futures_type - ) - def symbol_mark_price_socket( self, symbol: str, @@ -1097,12 +1080,7 @@ def options_depth_socket(self, symbol: str, depth: str = "10"): """ return self._get_options_socket(symbol.upper() + "@depth" + str(depth)) - def futures_depth_socket( - self, - symbol: str, - depth: str = "10", - futures_type=FuturesType.USD_M, - ): + def futures_depth_socket(self, symbol: str, depth: str = "10", futures_type=FuturesType.USD_M): """Subscribe to a futures depth data stream https://binance-docs.github.io/apidocs/futures/en/#partial-book-depth-streams From becd528d94a0d1ba82e284b85cf810b092a1aba6 Mon Sep 17 00:00:00 2001 From: Pablo Date: Sun, 23 Feb 2025 22:51:37 -0600 Subject: [PATCH 5/6] lint tests --- tests/test_streams_options.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_streams_options.py b/tests/test_streams_options.py index 1b2693e9e..77f1bd317 100644 --- a/tests/test_streams_options.py +++ b/tests/test_streams_options.py @@ -1,8 +1,6 @@ import sys import pytest from binance import BinanceSocketManager -from binance.async_client import AsyncClient -from .conftest import proxy, api_key, api_secret, testnet pytestmark = [ pytest.mark.skipif(sys.version_info < (3, 8), reason="websockets_proxy Python 3.8+"), From 337610f901f01f1973abd509b758fc35445d1bb7 Mon Sep 17 00:00:00 2001 From: Pablo Date: Sun, 23 Feb 2025 23:17:16 -0600 Subject: [PATCH 6/6] update timeout gh action --- .github/workflows/python-app.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 658baafff..a3a2d61a5 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -34,7 +34,7 @@ jobs: build: needs: lint runs-on: ubuntu-22.04 - timeout-minutes: 20 + timeout-minutes: 60 env: PROXY: "http://51.83.140.52:16301" TEST_TESTNET: "true"