Skip to content

Commit

Permalink
Merge pull request #623 from ttamg/feature/websockets-for-isolated-ma…
Browse files Browse the repository at this point in the history
…rgin

Added websockets functionality for isolated margin accounts
  • Loading branch information
kimchirichie committed Dec 6, 2020
2 parents cc28523 + fa6dfb8 commit 387b151
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 26 deletions.
13 changes: 9 additions & 4 deletions Endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,12 +475,17 @@
client.margin_stream_close(listenKey)
```
- **POST /sapi/v1/userDataStream/isolated** (Create a ListenKey (Isolated).)

> :warning: Not yet implemented
```python
client.isolated_margin_stream_get_listen_key(symbol)
```
- **PUT /sapi/v1/userDataStream/isolated** (Ping/Keep-alive a ListenKey (Isolated).)

> :warning: Not yet implemented
```python
client.isolated_margin_stream_keepalive(symbol, listenKey)
```
- **DELETE /sapi/v1/userDataStream/isolated** (Close a ListenKey (Isolated).)
```python
client.isolated_margin_stream_close(symbol, listenKey)
```
- *Savings Endpoints*
- **GET /sapi/v1/lending/daily/product/list (HMAC SHA256)** (Get Flexible Product List (USER_DATA).)
```python
Expand Down
97 changes: 90 additions & 7 deletions binance/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3424,12 +3424,14 @@ def get_max_margin_transfer(self, **params):
"""
return self._request_margin_api('get', 'margin/maxTransferable', signed=True, data=params)

# Cross-margin

def margin_stream_get_listen_key(self):
"""Start a new margin data stream and return the listen key
"""Start a new cross-margin data stream and return the listen key
If a stream already exists it should return the same key.
If the stream becomes invalid a new key is returned.
Can be used to keep the user stream alive.
Can be used to keep the stream alive.
https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#start-user-data-stream-for-margin-account-user_stream
Expand All @@ -3444,11 +3446,11 @@ def margin_stream_get_listen_key(self):
:raises: BinanceRequestException, BinanceAPIException
"""
res = self._request_margin_api('post', 'userDataStream', signed=True, data={})
res = self._request_margin_api('post', 'userDataStream', signed=False, data={})
return res['listenKey']

def margin_stream_keepalive(self, listenKey):
"""PING a margin data stream to prevent a time out.
"""PING a cross-margin data stream to prevent a time out.
https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#ping-user-data-stream-for-margin-account--user_stream
Expand All @@ -3467,10 +3469,10 @@ def margin_stream_keepalive(self, listenKey):
params = {
'listenKey': listenKey
}
return self._request_margin_api('put', 'userDataStream', signed=True, data=params)
return self._request_margin_api('put', 'userDataStream', signed=False, data=params)

def margin_stream_close(self, listenKey):
"""Close out a margin data stream.
"""Close out a cross-margin data stream.
https://github.com/binance-exchange/binance-official-api-docs/blob/master/margin-api.md#delete-user-data-stream-for-margin-account--user_stream
Expand All @@ -3489,7 +3491,88 @@ def margin_stream_close(self, listenKey):
params = {
'listenKey': listenKey
}
return self._request_margin_api('delete', 'userDataStream', signed=True, data=params)
return self._request_margin_api('delete', 'userDataStream', signed=False, data=params)

# Isolated margin

def isolated_margin_stream_get_listen_key(self, symbol):
"""Start a new isolated margin data stream and return the listen key
If a stream already exists it should return the same key.
If the stream becomes invalid a new key is returned.
Can be used to keep the stream alive.
https://binance-docs.github.io/apidocs/spot/en/#listen-key-isolated-margin
:param symbol: required - symbol for the isolated margin account
:type symbol: str
:returns: API response
.. code-block:: python
{
"listenKey": "T3ee22BIYuWqmvne0HNq2A2WsFlEtLhvWCtItw6ffhhdmjifQ2tRbuKkTHhr"
}
:raises: BinanceRequestException, BinanceAPIException
"""
params = {
'symbol': symbol
}
res = self._request_margin_api('post', 'userDataStream/isolated', signed=False, data=params)
return res['listenKey']

def isolated_margin_stream_keepalive(self, symbol, listenKey):
"""PING an isolated margin data stream to prevent a time out.
https://binance-docs.github.io/apidocs/spot/en/#listen-key-isolated-margin
:param symbol: required - symbol for the isolated margin account
:type symbol: str
:param listenKey: required
:type listenKey: str
:returns: API response
.. code-block:: python
{}
:raises: BinanceRequestException, BinanceAPIException
"""
params = {
'symbol': symbol,
'listenKey': listenKey
}
return self._request_margin_api('put', 'userDataStream/isolated', signed=False, data=params)

def isolated_margin_stream_close(self, symbol, listenKey):
"""Close out an isolated margin data stream.
https://binance-docs.github.io/apidocs/spot/en/#listen-key-isolated-margin
:param symbol: required - symbol for the isolated margin account
:type symbol: str
:param listenKey: required
:type listenKey: str
:returns: API response
.. code-block:: python
{}
:raises: BinanceRequestException, BinanceAPIException
"""
params = {
'symbol': symbol,
'listenKey': listenKey
}
return self._request_margin_api('delete', 'userDataStream/isolated', signed=False, data=params)

# Lending Endpoints

Expand Down
60 changes: 47 additions & 13 deletions binance/websockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@ def __init__(self, client, user_timeout=DEFAULT_USER_TIMEOUT):
self._conns = {}
self._client = client
self._user_timeout = user_timeout
self._timers = {'user': None, 'margin': None}
self._timers = {'user': None, 'margin': None}
self._listen_keys = {'user': None, 'margin': None}
self._account_callbacks = {'user': None, 'margin': None}
# Isolated margin sockets will be opened under the 'symbol' name

def _start_socket(self, path, callback, prefix='ws/'):
if path in self._conns:
Expand Down Expand Up @@ -590,6 +591,7 @@ def start_user_socket(self, callback):
"""Start a websocket for user data
https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md
https://binance-docs.github.io/apidocs/spot/en/#listen-key-spot
:param callback: callback function to handle messages
:type callback: function
Expand All @@ -604,9 +606,9 @@ def start_user_socket(self, callback):
return self._start_account_socket('user', user_listen_key, callback)

def start_margin_socket(self, callback):
"""Start a websocket for margin data
"""Start a websocket for cross-margin data
https://github.com/binance-exchange/binance-official-api-docs/blob/master/user-data-stream.md
https://binance-docs.github.io/apidocs/spot/en/#listen-key-margin
:param callback: callback function to handle messages
:type callback: function
Expand All @@ -620,6 +622,25 @@ def start_margin_socket(self, callback):
# and start the socket with this specific key
return self._start_account_socket('margin', margin_listen_key, callback)

def start_isolated_margin_socket(self, symbol, callback):
"""Start a websocket for isolated margin data
https://binance-docs.github.io/apidocs/spot/en/#listen-key-isolated-margin
:param symbol: required - symbol for the isolated margin account
:type symbol: str
:param callback: callback function to handle messages
:type callback: function
:returns: connection key string if successful, False otherwise
Message Format - see Binance API docs for all types
"""
# Get the isolated margin listen key
isolated_margin_listen_key = self._client.isolated_margin_stream_get_listen_key(symbol)
# and start the socket with this specific kek
return self._start_account_socket(symbol, isolated_margin_listen_key, callback)

def _start_account_socket(self, socket_type, listen_key, callback):
"""Starts one of user or margin socket"""
self._check_account_socket_open(listen_key)
Expand Down Expand Up @@ -650,10 +671,15 @@ def _keepalive_account_socket(self, socket_type):
if socket_type == 'user':
listen_key_func = self._client.stream_get_listen_key
callback = self._account_callbacks[socket_type]
else:
listen_key = listen_key_func()
elif socket_type == 'margin': # cross-margin
listen_key_func = self._client.margin_stream_get_listen_key
callback = self._account_callbacks[socket_type]
listen_key = listen_key_func()
listen_key = listen_key_func()
else: # isolated margin
listen_key_func = self._client.isolated_margin_stream_get_listen_key
callback = self._account_callbacks.get(socket_type, None)
listen_key = listen_key_func(socket_type) # Passing symbol for islation margin
if listen_key != self._listen_keys[socket_type]:
self._start_account_socket(socket_type, listen_key, callback)

Expand All @@ -673,18 +699,26 @@ def stop_socket(self, conn_key):
self._conns[conn_key].disconnect()
del(self._conns[conn_key])

# check if we have a user stream socket
if len(conn_key) >= 60 and conn_key[:60] == self._listen_keys['user']:
self._stop_account_socket('user')
# OBSOLETE - removed when adding isolated margin. Loop over keys instead
# # check if we have a user stream socket
# if len(conn_key) >= 60 and conn_key[:60] == self._listen_keys['user']:
# self._stop_account_socket('user')

# # or a margin stream socket
# if len(conn_key) >= 60 and conn_key[:60] == self._listen_keys['margin']:
# self._stop_account_socket('margin')

# NEW - Loop over keys in _listen_keys dictionary to find a match on
# user, cross-margin and isolated margin:
for key, value in self._listen_keys.items():
if len(conn_key) >= 60 and conn_key[:60] == value:
self._stop_account_socket(key)

# or a margin stream socket
if len(conn_key) >= 60 and conn_key[:60] == self._listen_keys['margin']:
self._stop_account_socket('margin')

def _stop_account_socket(self, socket_type):
if not self._listen_keys[socket_type]:
if not self._listen_keys.get(socket_type, None):
return
if self._timers[socket_type]:
if self._timers.get(socket_type, None):
self._timers[socket_type].cancel()
self._timers[socket_type] = None
self._listen_keys[socket_type] = None
Expand Down
26 changes: 24 additions & 2 deletions docs/websockets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,8 @@ Valid interval values are `defined as enums <enums.html>`_.
# set as 5000 to receive updates every 5 seconds
conn_key = bm.start_miniticker_socket(process_message, 5000)
`User Socket <binance.html#binance.websockets.BinanceSocketManager.start_user_socket>`_
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
User Socket
+++++++++++

This watches for 3 different user events

Expand All @@ -164,11 +164,33 @@ This watches for 3 different user events

The Manager handles keeping the socket alive.

There are separate sockets for Spot, Cross-margin and separate Isolated margin accounts.

`Spot trading <binance.html#binance.websockets.BinanceSocketManager.start_user_socket>`_
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: python
bm.start_user_socket(process_message)
`Cross-margin <binance.html#binance.websockets.BinanceSocketManager.start_margin_socket>`_
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: python
bm.start_margin_socket(process_message)
`Isolated margin <binance.html#binance.websockets.BinanceSocketManager.start_isolated_margin_socket>`_
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: python
bm.start_isolated_margin_socket(symbol, process_message)
`Close a Socket <binance.html#binance.websockets.BinanceSocketManager.stop_socket>`_
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Expand Down

0 comments on commit 387b151

Please sign in to comment.