Skip to content

session logon #1580

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
build:
needs: lint
runs-on: ubuntu-22.04
timeout-minutes: 60
timeout-minutes: 40
env:
PROXY: "http://51.83.140.52:16301"
TEST_TESTNET: "true"
Expand Down Expand Up @@ -67,19 +67,18 @@ jobs:
run: pyright
- name: Test with tox
run: tox -e py
# comment due to coveralls maintenance of April 6 2025: https://status.coveralls.io/
# - name: Coveralls Parallel
# uses: coverallsapp/github-action@v2
# with:
# flag-name: run-${{ join(matrix.*, '-') }}
# parallel: true
# finish:
# needs: build
# if: ${{ always() }}
# runs-on: ubuntu-latest
# timeout-minutes: 5
# steps:
# - name: Coveralls Finished
# uses: coverallsapp/github-action@v2
# with:
# parallel-finished: true
- name: Coveralls Parallel
uses: coverallsapp/github-action@v2
with:
flag-name: run-${{ join(matrix.*, '-') }}
parallel: true
finish:
needs: build
if: ${{ always() }}
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Coveralls Finished
uses: coverallsapp/github-action@v2
with:
parallel-finished: true
51 changes: 51 additions & 0 deletions binance/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3396,6 +3396,57 @@ async def create_oco_order(self, **params):
# WebSocket API methods
############################################################

async def ws_logon(self, **params):
"""Authenticate WebSocket connection using the provided API key.
https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/authentication-requests

:returns: WS response
.. code-block:: python
{
"apiKey": "vmPUZE6mv9SD5VNHk4HlWFsOr6aKE2zvsw0MuIgwCIPy6utIco14y7Ju91duEh8A",
"authorizedSince": 1649729878532,
"connectedSince": 1649729873021,
"returnRateLimits": false,
"serverTime": 1649729878630,
"userDataStream": false
}
"""
return await self._ws_api_request("session.logon", True, params)

async def ws_user_data_stream_subscribe(self, **params):
"""Subscribe to the User Data Stream in the current WebSocket connection.
https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/user-data-stream-requests#subscribe-to-user-data-stream-user_stream

Note:
- This method requires an authenticated WebSocket connection using Ed25519 keys. Please use ws_logon first.
- To check the subscription status, use ws_session_status, see the userDataStream flag.
- User Data Stream events are available in both JSON and SBE sessions.

:returns: WS response
.. code-block:: python
{
"id": "d3df8a21-98ea-4fe0-8f4e-0fcea5d418b7",
"status": 200,
"result": {}
}
"""
return await self._ws_api_request("userDataStream.subscribe", True, params)

async def ws_user_data_stream_unsubscribe(self, **params):
"""Stop listening to the User Data Stream in the current WebSocket connection.
https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/user-data-stream-requests#unsubscribe-from-user-data-stream-user_stream

:returns: WS response
.. code-block:: python
{
"id": "d3df8a21-98ea-4fe0-8f4e-0fcea5d418b7",
"status": 200,
"result": {}
}
"""
return await self._ws_api_request("userDataStream.unsubscribe", False, params)


async def ws_create_test_order(self, **params):
"""Test new order creation and signature/recvWindow long. Creates and validates a new order but does not send it into the matching engine.
https://binance-docs.github.io/apidocs/websocket_api/en/#test-new-order-trade
Expand Down
4 changes: 3 additions & 1 deletion binance/base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class BaseClient:
OPTIONS_TESTNET_URL = "https://testnet.binanceops.{}/eapi"
PAPI_URL = "https://papi.binance.{}/papi"
WS_API_URL = "wss://ws-api.binance.{}/ws-api/v3"
WS_API_TESTNET_URL = "wss://testnet.binance.vision/ws-api/v3"
WS_API_TESTNET_URL = "wss://ws-api.testnet.binance.vision/ws-api/v3"
WS_FUTURES_URL = "wss://ws-fapi.binance.{}/ws-fapi/v1"
WS_FUTURES_TESTNET_URL = "wss://testnet.binancefuture.com/ws-fapi/v1"
PUBLIC_API_VERSION = "v1"
Expand Down Expand Up @@ -405,6 +405,8 @@ async def _ws_api_request(self, method: str, signed: bool, params: dict):
payload["params"] = self._sign_ws_params(
params, self._generate_ws_api_signature
)
if method == "userDataStream.subscribe":
del payload["params"]
return await self.ws_api.request(id, payload)

def _ws_api_request_sync(self, method: str, signed: bool, params: dict):
Expand Down
49 changes: 49 additions & 0 deletions binance/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10898,6 +10898,55 @@ def __del__(self):
############################################################
# WebSocket API methods
############################################################
def ws_logon(self, **params):
"""Authenticate WebSocket connection using the provided API key.
https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/authentication-requests

:returns: WS response
.. code-block:: python
{
"apiKey": "vmPUZE6mv9SD5VNHk4HlWFsOr6aKE2zvsw0MuIgwCIPy6utIco14y7Ju91duEh8A",
"authorizedSince": 1649729878532,
"connectedSince": 1649729873021,
"returnRateLimits": false,
"serverTime": 1649729878630,
"userDataStream": false
}
"""
return self._ws_api_request_sync("session.logon", True, params)

def ws_user_data_stream_subscribe(self, **params):
"""Subscribe to the User Data Stream in the current WebSocket connection.
https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/user-data-stream-requests#subscribe-to-user-data-stream-user_stream

Note:
- This method requires an authenticated WebSocket connection using Ed25519 keys. Please use ws_logon first.
- To check the subscription status, use ws_session_status, see the userDataStream flag.
- User Data Stream events are available in both JSON and SBE sessions.

:returns: WS response
.. code-block:: python
{
"id": "d3df8a21-98ea-4fe0-8f4e-0fcea5d418b7",
"status": 200,
"result": {}
}
"""
return self._ws_api_request_sync("userDataStream.subscribe", True, params)

def ws_user_data_stream_unsubscribe(self, **params):
"""Stop listening to the User Data Stream in the current WebSocket connection.
https://developers.binance.com/docs/binance-spot-api-docs/websocket-api/user-data-stream-requests#unsubscribe-from-user-data-stream-user_stream

:returns: WS response
.. code-block:: python
{
"id": "d3df8a21-98ea-4fe0-8f4e-0fcea5d418b7",
"status": 200,
"result": {}
}
"""
return self._ws_api_request_sync("userDataStream.unsubscribe", False, params)

def ws_create_test_order(self, **params):
"""Test new order creation and signature/recvWindow long. Creates and validates a new order but does not send it into the matching engine.
Expand Down
22 changes: 21 additions & 1 deletion binance/ws/streams.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import threading

Check failure on line 2 in binance/ws/streams.py

View workflow job for this annotation

GitHub Actions / lint

Ruff (F401)

binance/ws/streams.py:2:8: F401 `threading` imported but unused
import time
from enum import Enum
from typing import Optional, List, Dict, Callable, Any
Expand All @@ -13,7 +14,9 @@
from binance.enums import FuturesType
from binance.enums import ContractType
from binance.helpers import get_loop
import logging

logger = logging.getLogger(__name__)

class BinanceSocketType(str, Enum):
SPOT = "Spot"
Expand All @@ -25,7 +28,7 @@

class BinanceSocketManager:
STREAM_URL = "wss://stream.binance.{}:9443/"
STREAM_TESTNET_URL = "wss://testnet.binance.vision/"
STREAM_TESTNET_URL = "wss://stream.testnet.binance.vision/"
FSTREAM_URL = "wss://fstream.binance.{}/"
FSTREAM_TESTNET_URL = "wss://stream.binancefuture.com/"
DSTREAM_URL = "wss://dstream.binance.{}/"
Expand Down Expand Up @@ -907,6 +910,21 @@
stream_url = self.STREAM_URL
if self.testnet:
stream_url = self.STREAM_TESTNET_URL
client = self._client
if client.PRIVATE_KEY and not client._is_rsa:
async def setup_ws():
try:
res = await client.ws_logon()
logger.debug("Logged on: %s", res)
res = await client.ws_user_data_stream_subscribe()
logger.debug("Subscribed to user data stream: %s", res)
except Exception as e:
logger.error("Error setting up user data stream: %s", e)
self._client.loop.call_soon_threadsafe(
asyncio.create_task, setup_ws()
)
# TODO: Fix to wait for setup_ws to complete
return client.ws_api
return self._get_account_socket("user", stream_url=stream_url)

def futures_user_socket(self):
Expand Down Expand Up @@ -1211,6 +1229,7 @@
https_proxy: Optional[str] = None,
loop: Optional[asyncio.AbstractEventLoop] = None,
max_queue_size: int = 100,
private_key: Optional[str] = None,
):
super().__init__(
api_key,
Expand All @@ -1221,6 +1240,7 @@
session_params,
https_proxy,
loop,
private_key,
)
self._bsm: Optional[BinanceSocketManager] = None
self._max_queue_size = max_queue_size
Expand Down
2 changes: 2 additions & 0 deletions binance/ws/threaded_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def __init__(
session_params: Optional[Dict[str, Any]] = None,
https_proxy: Optional[str] = None,
_loop: Optional[asyncio.AbstractEventLoop] = None,
private_key: Optional[str] = None,
):
"""Initialise the BinanceSocketManager"""
super().__init__()
Expand All @@ -34,6 +35,7 @@ def __init__(
"testnet": testnet,
"session_params": session_params,
"https_proxy": https_proxy,
"private_key": private_key,
}

async def _before_socket_listener_start(self): ...
Expand Down
2 changes: 1 addition & 1 deletion binance/ws/websocket_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def _handle_message(self, msg):
elif exception is not None:
raise exception
else:
self._log.warning(f"WS api receieved unknown message: {parsed_msg}")
return parsed_msg

async def _ensure_ws_connection(self) -> None:
"""Ensure WebSocket connection is established and ready
Expand Down
33 changes: 21 additions & 12 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
import pytest_asyncio
from binance.client import Client
from binance.async_client import AsyncClient
import os
Expand All @@ -21,8 +22,10 @@
futures_api_key = os.getenv("TEST_FUTURES_API_KEY")
futures_api_secret = os.getenv("TEST_FUTURES_API_SECRET")
testnet = os.getenv("TEST_TESTNET", "true").lower() == "true"
api_key = "u4L8MG2DbshTfTzkx2Xm7NfsHHigvafxeC29HrExEmah1P8JhxXkoOu6KntLICUc"
api_secret = "hBZEqhZUUS6YZkk7AIckjJ3iLjrgEFr5CRtFPp5gjzkrHKKC9DAv4OH25PlT6yq5"
# api_key = "u4L8MG2DbshTfTzkx2Xm7NfsHHigvafxeC29HrExEmah1P8JhxXkoOu6KntLICUc"
# api_secret = "hBZEqhZUUS6YZkk7AIckjJ3iLjrgEFr5CRtFPp5gjzkrHKKC9DAv4OH25PlT6yq5"
api_key = "c5CTLgB3lqajkoN7OD28RG5sLOsVLeK4o0Ca9kT64mV6F8mKMwFeAB7uBJFRL8pG"
api_secret = "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIMwY1k3A4awroEHcfCdrn/Azan+ZgJqGhlNep3/Bud3e\n-----END PRIVATE KEY-----"
testnet = True
futures_api_key = "227719da8d8499e8d3461587d19f259c0b39c2b462a77c9b748a6119abd74401"
futures_api_secret = "b14b935f9cfacc5dec829008733c40da0588051f29a44625c34967b45c11d73c"
Expand All @@ -46,7 +49,7 @@ def setup_logging():

@pytest.fixture(scope="function")
def client():
return Client(api_key, api_secret, {"proxies": proxies}, testnet=testnet)
return Client(api_key, api_secret, {"proxies": proxies}, testnet=testnet, private_key=api_secret)


@pytest.fixture(scope="function")
Expand All @@ -61,21 +64,27 @@ def futuresClient():
)


@pytest.fixture(scope="function")
def clientAsync():
return AsyncClient(api_key, api_secret, https_proxy=proxy, testnet=testnet)
@pytest_asyncio.fixture(scope="function")
async def clientAsync():
client = AsyncClient(api_key, api_secret, https_proxy=proxy, testnet=testnet, private_key=api_secret)
yield client
await client.close_connection()


@pytest.fixture(scope="function")
def futuresClientAsync():
return AsyncClient(
@pytest_asyncio.fixture(scope="function")
async def futuresClientAsync():
client = AsyncClient(
futures_api_key, futures_api_secret, https_proxy=proxy, testnet=testnet
)
yield client
await client.close_connection()


@pytest.fixture(scope="function")
def liveClientAsync():
return AsyncClient(api_key, api_secret, https_proxy=proxy, testnet=False)
@pytest_asyncio.fixture(scope="function")
async def liveClientAsync():
client = AsyncClient(api_key, api_secret, https_proxy=proxy, testnet=False)
yield client
await client.close_connection()

@pytest.fixture(scope="function")
def manager():
Expand Down
Loading
Loading