Skip to content

Commit

Permalink
Merge pull request binance-exchange#1 from sammchardy/master
Browse files Browse the repository at this point in the history
update to latest
  • Loading branch information
flashnuke authored Jan 2, 2021
2 parents 2c2b3bf + 3f3cc65 commit e25b81e
Show file tree
Hide file tree
Showing 11 changed files with 1,714 additions and 131 deletions.
778 changes: 778 additions & 0 deletions Endpoints.md

Large diffs are not rendered by default.

9 changes: 3 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
================================
Welcome to python-binance v0.7.5
Welcome to python-binance v0.7.8
================================

Note
----

I am working on a python3 version with async support to remove legacy dependencies and their related issues.

I would appreciate if you could try out the `feature/asyncio <https://github.com/sammchardy/python-binance/tree/feature/asyncio>`_ branch and give your feedback.

this library is not under active development by sammchardy. however, the community has been actively contributing lots of PRs. If you find missing features please submit a PR. please keep PRs small and non-breaking.

.. image:: https://img.shields.io/pypi/v/python-binance.svg
:target: https://pypi.python.org/pypi/python-binance
Expand All @@ -28,7 +25,7 @@ I would appreciate if you could try out the `feature/asyncio <https://github.com
.. image:: https://img.shields.io/pypi/pyversions/python-binance.svg
:target: https://pypi.python.org/pypi/python-binance

This is an unofficial Python wrapper for the `Binance exchange REST API v1/3 <https://github.com/binance-exchange/binance-official-api-docs>`_. I am in no way affiliated with Binance, use at your own risk.
This is an unofficial Python wrapper for the `Binance exchange REST API v3 <https://github.com/binance/binance-spot-api-docs>`_. I am in no way affiliated with Binance, use at your own risk.

If you came here looking for the `Binance exchange <https://www.binance.com/?ref=10099792>`_ to purchase cryptocurrencies, then `go here <https://www.binance.com/?ref=10099792>`_. If you want to automate interactions with Binance stick around.

Expand Down
731 changes: 639 additions & 92 deletions binance/client.py

Large diffs are not rendered by default.

14 changes: 12 additions & 2 deletions binance/depthcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ class DepthCacheManager(object):

_default_refresh = 60 * 30 # 30 minutes

def __init__(self, client, symbol, callback=None, refresh_interval=_default_refresh, bm=None, limit=500):
def __init__(self, client, symbol, callback=None, refresh_interval=_default_refresh, bm=None, limit=500, ws_interval=None):
"""Initialise the DepthCacheManager
:param client: Binance API client
Expand All @@ -134,6 +134,8 @@ def __init__(self, client, symbol, callback=None, refresh_interval=_default_refr
:type refresh_interval: int
:param limit: Optional number of orders to get from orderbook
:type limit: int
:param ws_interval: Optional interval for updates on websocket, default None. If not set, updates happen every second. Must be 0, None (1s) or 100 (100ms).
:type ws_interval: int
"""
self._client = client
Expand All @@ -146,6 +148,7 @@ def __init__(self, client, symbol, callback=None, refresh_interval=_default_refr
self._depth_cache = DepthCache(self._symbol)
self._refresh_interval = refresh_interval
self._conn_key = None
self._ws_interval = ws_interval

self._start_socket()
self._init_cache()
Expand Down Expand Up @@ -188,7 +191,7 @@ def _start_socket(self):
if self._bm is None:
self._bm = BinanceSocketManager(self._client)

self._conn_key = self._bm.start_depth_socket(self._symbol, self._depth_event)
self._conn_key = self._bm.start_depth_socket(self._symbol, self._depth_event, interval=self._ws_interval)
if not self._bm.is_alive():
self._bm.start()

Expand Down Expand Up @@ -271,3 +274,10 @@ def close(self, close_socket=False):
self._bm.close()
time.sleep(1)
self._depth_cache = None

def get_symbol(self):
"""Get the symbol
:return: symbol
"""
return self._symbol
177 changes: 163 additions & 14 deletions binance/websockets.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def clientConnectionLost(self, connector, reason):
class BinanceSocketManager(threading.Thread):

STREAM_URL = 'wss://stream.binance.com:9443/'
FSTREAM_URL = 'wss://fstream.binance.com/'

WEBSOCKET_DEPTH_5 = '5'
WEBSOCKET_DEPTH_10 = '10'
Expand All @@ -84,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 All @@ -102,7 +104,21 @@ def _start_socket(self, path, callback, prefix='ws/'):
self._conns[path] = connectWS(factory, context_factory)
return path

def start_depth_socket(self, symbol, callback, depth=None):
def _start_futures_socket(self, path, callback, prefix='stream?streams='):
if path in self._conns:
return False

factory_url = self.FSTREAM_URL + prefix + path
factory = BinanceClientFactory(factory_url)
factory.protocol = BinanceClientProtocol
factory.callback = callback
factory.reconnect = True
context_factory = ssl.ClientContextFactory()

self._conns[path] = connectWS(factory, context_factory)
return path

def start_depth_socket(self, symbol, callback, depth=None, interval=None):
"""Start a websocket for symbol market depth returning either a diff or a partial book
https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md#partial-book-depth-streams
Expand All @@ -113,6 +129,8 @@ def start_depth_socket(self, symbol, callback, depth=None):
:type callback: function
:param depth: optional Number of depth entries to return, default None. If passed returns a partial book instead of a diff
:type depth: str
:param interval: optional interval for updates, default None. If not set, updates happen every second. Must be 0, None (1s) or 100 (100ms)
:type interval: int
:returns: connection key string if successful, False otherwise
Expand Down Expand Up @@ -169,6 +187,11 @@ def start_depth_socket(self, symbol, callback, depth=None):
socket_name = symbol.lower() + '@depth'
if depth and depth != '1':
socket_name = '{}{}'.format(socket_name, depth)
if interval:
if interval in [0, 100]:
socket_name = '{}@{}ms'.format(socket_name, interval)
else:
raise ValueError("Websocket interval value not allowed. Allowed values are [0, 100]")
return self._start_socket(socket_name, callback)

def start_kline_socket(self, symbol, callback, interval=Client.KLINE_INTERVAL_1MINUTE):
Expand Down Expand Up @@ -406,6 +429,99 @@ def start_ticker_socket(self, callback):
"""
return self._start_socket('!ticker@arr', callback)

def start_symbol_mark_price_socket(self, symbol, callback, fast=True):
"""Start a websocket for a symbol's futures mark price
https://binance-docs.github.io/apidocs/futures/en/#mark-price-stream
:param symbol: required
:type symbol: str
:param callback: callback function to handle messages
:type callback: function
:returns: connection key string if successful, False otherwise
Message Format
.. code-block:: python
{
"e": "markPriceUpdate", // Event type
"E": 1562305380000, // Event time
"s": "BTCUSDT", // Symbol
"p": "11185.87786614", // Mark price
"r": "0.00030000", // Funding rate
"T": 1562306400000 // Next funding time
}
"""
stream_name = '@markPrice@1s' if fast else '@markPrice'
return self._start_futures_socket(symbol.lower() + stream_name, callback)

def start_all_mark_price_socket(self, callback, fast=True):
"""Start a websocket for all futures mark price data
By default all symbols are included in an array.
https://binance-docs.github.io/apidocs/futures/en/#mark-price-stream-for-all-market
:param callback: callback function to handle messages
:type callback: function
:returns: connection key string if successful, False otherwise
Message Format
.. code-block:: python
[
{
"e": "markPriceUpdate", // Event type
"E": 1562305380000, // Event time
"s": "BTCUSDT", // Symbol
"p": "11185.87786614", // Mark price
"r": "0.00030000", // Funding rate
"T": 1562306400000 // Next funding time
}
]
"""
stream_name = '!markPrice@arr@1s' if fast else '!markPrice@arr'
return self._start_futures_socket(stream_name, callback)

def start_symbol_ticker_futures_socket(self, symbol, callback):
"""Start a websocket for a symbol's ticker data
By default all markets are included in an array.
https://binance-docs.github.io/apidocs/futures/en/#individual-symbol-book-ticker-streams
:param symbol: required
:type symbol: str
:param callback: callback function to handle messages
:type callback: function
:returns: connection key string if successful, False otherwise
.. code-block:: python
[
{
"u":400900217, // order book updateId
"s":"BNBUSDT", // symbol
"b":"25.35190000", // best bid price
"B":"31.21000000", // best bid qty
"a":"25.36520000", // best ask price
"A":"40.66000000" // best ask qty
}
]
"""
return self._start_futures_socket(symbol.lower() + '@bookTicker', callback)

def start_all_ticker_futures_socket(self, callback):
"""Start a websocket for all ticker data
By default all markets are included in an array.
https://binance-docs.github.io/apidocs/futures/en/#all-book-tickers-stream
:param callback: callback function to handle messages
:type callback: function
:returns: connection key string if successful, False otherwise
Message Format
.. code-block:: python
[
{
"u":400900217, // order book updateId
"s":"BNBUSDT", // symbol
"b":"25.35190000", // best bid price
"B":"31.21000000", // best bid qty
"a":"25.36520000", // best ask price
"A":"40.66000000" // best ask qty
}
]
"""


return self._start_futures_socket('!bookTicker', callback)

def start_symbol_book_ticker_socket(self, symbol, callback):
"""Start a websocket for the best bid or ask's price or quantity for a specified symbol.
Expand Down Expand Up @@ -482,6 +598,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 @@ -496,9 +613,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 @@ -512,6 +629,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 @@ -542,10 +678,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 @@ -565,18 +706,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
2 changes: 1 addition & 1 deletion docs/account.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Order Validation

Binance has a number of rules around symbol pair orders with validation on minimum price, quantity and total order value.

Read more about their specifics in the `Filters <https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#filters>`_
Read more about their specifics in the `Filters <https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md#filters>`_
section of the official API.

It can be helpful to format the output using the following snippet
Expand Down
5 changes: 5 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
=========

v0.7.5.dev
^^^^^^^^^^
**Changed**
- Stock json lib to ujson (https://github.com/sammchardy/python-binance/pull/383)

v0.7.5 - 2020-02-06
^^^^^^^^^^^^^^^^^^^

Expand Down
Loading

0 comments on commit e25b81e

Please sign in to comment.