├── requirements.txt ├── setup.cfg ├── bittrex_websocket ├── __init__.py ├── _exceptions.py ├── _auxiliary.py ├── _queue_events.py ├── _logger.py ├── constants.py ├── _abc.py └── websocket_client.py ├── examples ├── account_level_data.py ├── record_trades.py └── ticker_updates.py ├── LICENSE ├── setup.py ├── .gitignore ├── resources ├── archieved_changelog.txt ├── py_btrx.svg └── README_OLD.md └── README.rst /requirements.txt: -------------------------------------------------------------------------------- 1 | websockets>=6.0 2 | signalr-client-aio=0.0.1.6.1 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | 4 | [metadata] 5 | description-file = README.md -------------------------------------------------------------------------------- /bittrex_websocket/__init__.py: -------------------------------------------------------------------------------- 1 | from bittrex_websocket import _logger 2 | from bittrex_websocket.websocket_client import BittrexSocket 3 | from bittrex_websocket.constants import BittrexMethods 4 | -------------------------------------------------------------------------------- /bittrex_websocket/_exceptions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # bittrex_websocket/_exceptions.py 5 | # Stanislav Lazarov 6 | 7 | from websockets.exceptions import ConnectionClosed, InvalidStatusCode 8 | from requests.exceptions import ConnectionError 9 | -------------------------------------------------------------------------------- /examples/account_level_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # bittrex_websocket/examples/account_level_data.py 5 | # Stanislav Lazarov 6 | 7 | # Sample script showing how to subscribe to the private callbacks (Balance Delta and Order Delta) of Bittrex. 8 | # You need to provide your api_key and api_secret with the respective permissions. 9 | # For more info check _abc.py or https://github.com/Bittrex/beta 10 | 11 | 12 | from bittrex_websocket.websocket_client import BittrexSocket 13 | from time import sleep 14 | 15 | 16 | def main(): 17 | class MySocket(BittrexSocket): 18 | 19 | async def on_private(self, msg): 20 | print(msg) 21 | 22 | # Create the socket instance 23 | ws = MySocket() 24 | 25 | # Enable logging 26 | ws.enable_log() 27 | ws.authenticate('### API KEY ###', '### API SECRET ###') 28 | 29 | while True: 30 | sleep(10) 31 | 32 | 33 | if __name__ == "__main__": 34 | main() 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Stanislav Lazarov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bittrex_websocket/_auxiliary.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # bittrex_websocket/_auxiliary.py 5 | # Stanislav Lazarov 6 | 7 | import logging 8 | from uuid import uuid4 9 | import hashlib 10 | import hmac 11 | 12 | try: 13 | from pybase64 import b64decode, b64encode 14 | except: 15 | from base64 import b64decode, b64encode 16 | from zlib import decompress, MAX_WBITS 17 | 18 | try: 19 | from ujson import loads 20 | except: 21 | from json import loads 22 | 23 | logger = logging.getLogger(__name__) 24 | 25 | 26 | async def process_message(message): 27 | try: 28 | deflated_msg = decompress(b64decode(message, validate=True), -MAX_WBITS) 29 | except SyntaxError: 30 | deflated_msg = decompress(b64decode(message, validate=True)) 31 | return loads(deflated_msg.decode()) 32 | 33 | 34 | async def create_signature(api_secret, challenge): 35 | api_sign = hmac.new(api_secret.encode(), challenge.encode(), hashlib.sha512).hexdigest() 36 | return api_sign 37 | 38 | 39 | class BittrexConnection(object): 40 | def __init__(self, conn, hub): 41 | self.conn = conn 42 | self.corehub = hub 43 | self.id = uuid4().hex 44 | -------------------------------------------------------------------------------- /bittrex_websocket/_queue_events.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # bittrex_websocket/_queue_events.py 5 | # Stanislav Lazarov 6 | 7 | from .constants import EventTypes 8 | 9 | 10 | class Event(object): 11 | """ 12 | Event is base class providing an interface 13 | for all subsequent(inherited) events. 14 | """ 15 | 16 | 17 | class ConnectEvent(Event): 18 | """ 19 | Handles the event of creating a new connection. 20 | """ 21 | 22 | def __init__(self): 23 | self.type = EventTypes.CONNECT 24 | 25 | 26 | class SubscribeEvent(Event): 27 | """ 28 | Handles the event of subscribing specific ticker(s) to specific channels. 29 | """ 30 | 31 | def __init__(self, invoke, *payload): 32 | self.type = EventTypes.SUBSCRIBE 33 | self.invoke = invoke 34 | self.payload = payload 35 | 36 | 37 | class ReconnectEvent(Event): 38 | """ 39 | Handles the event reconnection. 40 | """ 41 | 42 | def __init__(self, error_message): 43 | self.type = EventTypes.RECONNECT 44 | self.error_message = error_message 45 | 46 | 47 | class CloseEvent(Event): 48 | """ 49 | Handles the event of closing the socket. 50 | """ 51 | 52 | def __init__(self): 53 | self.type = EventTypes.CLOSE 54 | -------------------------------------------------------------------------------- /bittrex_websocket/_logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from logging import NullHandler 3 | 4 | logging.getLogger(__package__).addHandler(NullHandler()) 5 | 6 | 7 | def add_stream_logger(level=logging.DEBUG, file_name=None): 8 | logger = logging.getLogger(__package__) 9 | logger.setLevel(level) 10 | if file_name is not None: 11 | logger.addHandler(_get_file_handler(file_name)) 12 | logger.addHandler(_get_stream_handler()) 13 | 14 | 15 | def remove_stream_logger(): 16 | logger = logging.getLogger(__package__) 17 | logger.propagate = False 18 | for handler in list(logger.handlers): 19 | logger.removeHandler(handler) 20 | logger.addHandler(NullHandler()) 21 | 22 | 23 | def _get_stream_handler(level=logging.DEBUG): 24 | handler = logging.StreamHandler() 25 | handler.setLevel(level) 26 | handler.setFormatter(_get_formatter()) 27 | return handler 28 | 29 | 30 | def _get_file_handler(file_name): 31 | file_handler = logging.FileHandler(file_name) 32 | file_handler.setFormatter(_get_formatter()) 33 | return file_handler 34 | 35 | 36 | def _get_formatter(): 37 | msg_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' 38 | date_format = '%Y-%m-%d %H:%M:%S' 39 | formatter = logging.Formatter(msg_format, date_format) 40 | return formatter 41 | -------------------------------------------------------------------------------- /bittrex_websocket/constants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # bittrex_websocket/constants.py 5 | # Stanislav Lazarov 6 | 7 | 8 | class Constant(object): 9 | """ 10 | Event is base class providing an interface 11 | for all subsequent(inherited) constants. 12 | """ 13 | 14 | 15 | class EventTypes(Constant): 16 | CONNECT = 'CONNECT' 17 | SUBSCRIBE = 'SUBSCRIBE' 18 | CLOSE = 'CLOSE' 19 | RECONNECT = 'RECONNECT' 20 | 21 | 22 | class BittrexParameters(Constant): 23 | # Connection parameters 24 | URL = 'https://socket.bittrex.com/signalr' 25 | HUB = 'c2' 26 | # Callbacks 27 | MARKET_DELTA = 'uE' 28 | SUMMARY_DELTA = 'uS' 29 | SUMMARY_DELTA_LITE = 'uL' 30 | BALANCE_DELTA = 'uB' 31 | ORDER_DELTA = 'uO' 32 | 33 | 34 | class BittrexMethods(Constant): 35 | SUBSCRIBE_TO_EXCHANGE_DELTAS = 'SubscribeToExchangeDeltas' 36 | SUBSCRIBE_TO_SUMMARY_DELTAS = 'SubscribeToSummaryDeltas' 37 | SUBSCRIBE_TO_SUMMARY_LITE_DELTAS = 'SubscribeToSummaryLiteDeltas' 38 | QUERY_SUMMARY_STATE = 'QuerySummaryState' 39 | QUERY_EXCHANGE_STATE = 'QueryExchangeState' 40 | GET_AUTH_CONTENT = 'GetAuthContext' 41 | AUTHENTICATE = 'Authenticate' 42 | 43 | 44 | class ErrorMessages(Constant): 45 | INVALID_TICKER_INPUT = 'Tickers must be submitted as a list.' 46 | 47 | 48 | class OtherConstants(Constant): 49 | CF_SESSION_TYPE = '' 50 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | install_requires = \ 6 | [ 7 | 'websockets>=6.0', 8 | 'signalr-client-aio>=0.0.1.6.1' 9 | ] 10 | 11 | setup( 12 | name='bittrex-websocket-aio', 13 | version='0.0.0.3.0', 14 | author='Stanislav Lazarov', 15 | author_email='s.a.lazarov@gmail.com', 16 | license='MIT', 17 | url='https://github.com/slazarov/python-bittrex-websocket-aio', 18 | packages=find_packages(exclude=['tests*']), 19 | install_requires=install_requires, 20 | description='The unofficial Python websocket (AsyncIO) client for the Bittrex Cryptocurrency Exchange', 21 | download_url='https://github.com/slazarov/python-bittrex-websocket-aio.git', 22 | keywords=['bittrex', 'bittrex-websocket', 'orderbook', 'trade', 'bitcoin', 'ethereum', 'BTC', 'ETH', 'client', 23 | 'websocket', 'exchange', 'crypto', 'currency', 'trading', 'async', 'aio'], 24 | classifiers=[ 25 | 'Development Status :: 3 - Alpha', 26 | 'Intended Audience :: Developers', 27 | 'Intended Audience :: Financial and Insurance Industry', 28 | 'Intended Audience :: Information Technology', 29 | 'Topic :: Software Development :: Libraries :: Python Modules', 30 | 'License :: OSI Approved :: MIT License', 31 | 'Programming Language :: Python :: 3.5', 32 | 'Programming Language :: Python :: 3.6' 33 | ] 34 | ) 35 | -------------------------------------------------------------------------------- /examples/record_trades.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # /examples/record_trades.py 5 | # Stanislav Lazarov 6 | 7 | # Sample script showing how subscribe_to_exchange_deltas() works. 8 | 9 | # Overview: 10 | # --------- 11 | # 1) Creates custom trade_history dict. 12 | # 2) When an order is executed, the fill is recorded in trade_history. 13 | # 3) When each ticker has received an order, the script disconnects. 14 | 15 | from __future__ import print_function 16 | from time import sleep 17 | from bittrex_websocket import BittrexSocket, BittrexMethods 18 | 19 | 20 | def main(): 21 | class MySocket(BittrexSocket): 22 | 23 | async def on_public(self, msg): 24 | # Create entry for the ticker in the trade_history dict 25 | if msg['invoke_type'] == BittrexMethods.SUBSCRIBE_TO_EXCHANGE_DELTAS: 26 | if msg['M'] not in trade_history: 27 | trade_history[msg['M']] = [] 28 | # Add history nounce 29 | trade_history[msg['M']].append(msg) 30 | # Ping 31 | print('[Trades]: {}'.format(msg['M'])) 32 | 33 | # Create container 34 | trade_history = {} 35 | # Create the socket instance 36 | ws = MySocket() 37 | # Enable logging 38 | ws.enable_log() 39 | # Define tickers 40 | tickers = ['BTC-ETH', 'BTC-NEO', 'BTC-ZEC', 'ETH-NEO', 'ETH-ZEC'] 41 | # Subscribe to trade fills 42 | ws.subscribe_to_exchange_deltas(tickers) 43 | 44 | while len(set(tickers) - set(trade_history)) > 0: 45 | sleep(1) 46 | else: 47 | for ticker in trade_history.keys(): 48 | print('Printing {} trade history.'.format(ticker)) 49 | for trade in trade_history[ticker]: 50 | print(trade) 51 | ws.disconnect() 52 | sleep(10) 53 | 54 | 55 | if __name__ == "__main__": 56 | main() 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/python 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | .pytest_cache/ 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | .hypothesis/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule.* 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # Environments 83 | .env 84 | .venv 85 | env/ 86 | venv/ 87 | ENV/ 88 | env.bak/ 89 | venv.bak/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # Other 105 | .idea/ 106 | tests/ 107 | pypi_build.sh 108 | 109 | # End of https://www.gitignore.io/api/python 110 | -------------------------------------------------------------------------------- /examples/ticker_updates.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # /examples/ticker_updates.py 5 | # Stanislav Lazarov 6 | 7 | # Sample script showing how subscribe_to_exchange_deltas() works. 8 | 9 | # Overview: 10 | # --------- 11 | # 1) Creates a custom ticker_updates_container dict. 12 | # 2) Subscribes to N tickers and starts receiving market data. 13 | # 3) When information is received, checks if the ticker is 14 | # in ticker_updates_container and adds it if not. 15 | # 4) Disconnects when it has data information for each ticker. 16 | 17 | from bittrex_websocket.websocket_client import BittrexSocket 18 | from time import sleep 19 | 20 | 21 | def main(): 22 | class MySocket(BittrexSocket): 23 | 24 | async def on_public(self, msg): 25 | name = msg['M'] 26 | if name not in ticker_updates_container: 27 | ticker_updates_container[name] = msg 28 | print('Just received market update for {}.'.format(name)) 29 | 30 | # Create container 31 | ticker_updates_container = {} 32 | # Create the socket instance 33 | ws = MySocket() 34 | # Enable logging 35 | ws.enable_log() 36 | # Define tickers 37 | tickers = ['BTC-ETH', 'BTC-NEO', 'BTC-ZEC', 'ETH-NEO', 'ETH-ZEC'] 38 | # Subscribe to ticker information 39 | for ticker in tickers: 40 | sleep(0.01) 41 | ws.subscribe_to_exchange_deltas([ticker]) 42 | 43 | # Users can also subscribe without introducing delays during invoking but 44 | # it is the recommended way when you are subscribing to a large list of tickers. 45 | # ws.subscribe_to_exchange_deltas(tickers) 46 | 47 | while len(ticker_updates_container) < len(tickers): 48 | sleep(1) 49 | else: 50 | print('We have received updates for all tickers. Closing...') 51 | ws.disconnect() 52 | sleep(10) 53 | 54 | 55 | if __name__ == "__main__": 56 | main() 57 | -------------------------------------------------------------------------------- /resources/archieved_changelog.txt: -------------------------------------------------------------------------------- 1 | 0.0.7.3 - 06/04/2018 2 | - Set cfscrape >=1.9.5 3 | 4 | 0.0.7.2 - 31/03/2018 5 | - Added third connection URL. 6 | 7 | 0.0.7.1 - 31/03/2018 8 | -Removed wsaccel: no particular socket benefits 9 | - Fixed RecursionError as per Issue #52 10 | 11 | 0.0.7.0 - 25/02/2018 12 | - New reconnection methods implemented. Problem was within gevent, because connection failures within it are not raised in the main script. 13 | - Added wsaccel for better socket performance. 14 | - Set websocket-client minimum version to 0.47.0 15 | 16 | 0.0.6.4 - 24/02/2018 17 | - Fixed order book syncing bug when more than 1 connection is online due to wrong connection/thread name. 18 | 19 | 0.0.6.3 - 18/02/2018 20 | - Major changes to how the code handles order book syncing. Syncing is done significantly faster than previous versions, i.e full sync of all Bittrex tickers takes ca. 4 minutes. 21 | - Fixed on_open bug as per Issue #21 22 | 23 | 0.0.6.2.2 24 | - Update cfscrape>=1.9.2 and gevent>=1.3a1 25 | - Reorder imports in websocket_client to safeguard against SSL recursion errors. 26 | 27 | 0.0.6.2 28 | - Every 5400s (1hr30) the script will force reconnection. 29 | - Every reconnection (including the above) will be done with a fresh cookie 30 | - Upon reconnection the script will check if the connection has been running for more than 600s (10mins). If it has been running for less it will use the backup url. 31 | 32 | 0.0.6.1 33 | - Set websocket-client==0.46.0 34 | 35 | 0.0.6 36 | - Reconnection - Experimental 37 | - Fixed a bug when subscribing to multiple subscription types at once resulted in opening unnecessary connections even though there is sufficient capacity in the existing Commit 7fd21c 38 | - Numerous code optimizations 39 | 40 | 0.0.5.1 41 | - Updated cfscrape minimum version requirement (Issue #12). 42 | 43 | 0.0.5 44 | - Fixed Issue #9 relating to subscribe_to_orderbook_update handling in internal method _on_tick_update 45 | - Added custom logger as per PR #10 and Issue #8 in order to avoid conflicts with other basicConfig setups 46 | - Added two new methods enable_log and disable_log. Check Other Methods. 47 | - Logging is now disabled on startup. You have to enable it. 48 | - Experimental: Calling subscribe_to_ticker_update without a specified ticker subscribes to all tickers in the message stream (Issue #4). 49 | - Minor code optimizations (removed unnecessary class Common) 50 | 51 | 0.0.4 52 | - Changed the behaviour of how on_ticker_update channel works: The message now contains a single ticker instead of a dictionary of all subscribed tickers. 53 | 54 | 0.0.3 55 | - Removed left over code from initial release version that was throwing errors (had no effect on performance). 56 | 57 | 0.0.2 58 | - Additional un/subscribe and order book sync state querying methods added. 59 | - Better connection and thread management. 60 | - Code optimisations 61 | - Better code documentation 62 | - Added additional connection URLs 63 | 64 | 0.0.1 - Initial release on github. -------------------------------------------------------------------------------- /resources/py_btrx.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | |logo| bittrex-websocket-aio 2 | ============================ 3 | 4 | |pypi-v| |pypi-pyversions| |pypi-l| |pypi-wheel| 5 | 6 | .. |pypi-v| image:: https://img.shields.io/pypi/v/bittrex-websocket-aio.svg 7 | :target: https://pypi.python.org/pypi/bittrex-websocket-aio 8 | 9 | .. |pypi-pyversions| image:: https://img.shields.io/pypi/pyversions/bittrex-websocket-aio.svg 10 | :target: https://pypi.python.org/pypi/bittrex-websocket-aio 11 | 12 | .. |pypi-l| image:: https://img.shields.io/pypi/l/bittrex-websocket-aio.svg 13 | :target: https://pypi.python.org/pypi/bittrex-websocket-aio 14 | 15 | .. |pypi-wheel| image:: https://img.shields.io/pypi/wheel/bittrex-websocket-aio.svg 16 | :target: https://pypi.python.org/pypi/bittrex-websocket-aio 17 | 18 | .. |logo| image:: /resources/py_btrx.svg 19 | :width: 60px 20 | 21 | What is ``bittrex-websocket-aio``? 22 | ---------------------------------- 23 | Python Bittrex WebSocket (PBW) is the first unofficial Python wrapper for 24 | the `Bittrex Websocket API `_. 25 | It provides users with a simple and easy to use interface to the `Bittrex Exchange `_. 26 | 27 | Users can use it to access real-time public data (e.g exchange status, summary ticks and order fills) and account-level data such as order and balance status. The goal of the package is to serve as a foundation block which users can use to build upon their applications. Examples usages can include maintaining live order books, recording trade history, analysing order flow and many more. 28 | 29 | The version is built upon ``asyncio`` which is Python's standard asynchronous I/O framework. 30 | 31 | If you are looking for a ``non-async`` version or you are using Python=2.7, then take a look at my other library: `bittrex-websocket `_. 32 | 33 | -------------- 34 | 35 | Documentation 36 | http://python-bittrex-websocket-docs.readthedocs.io/en/latest/ 37 | 38 | Getting started/How-to 39 | http://python-bittrex-websocket-docs.readthedocs.io/en/latest/howto.html 40 | 41 | Methods 42 | http://python-bittrex-websocket-docs.readthedocs.io/en/latest/methods.html 43 | 44 | Changelog 45 | http://python-bittrex-websocket-docs.readthedocs.io/en/latest/changelog.html#bittrex-websocket-aio 46 | 47 | I am constantly working on new features. Make sure you stay up to date by regularly checking the official docs! 48 | 49 | **Having an issue or a question? Found a bug or perhaps you want to contribute? Open an issue!** 50 | 51 | Quick Start 52 | ----------- 53 | .. code:: bash 54 | 55 | pip install bittrex-websocket-aio 56 | 57 | .. code:: python 58 | 59 | #!/usr/bin/python 60 | # /examples/ticker_updates.py 61 | 62 | # Sample script showing how subscribe_to_exchange_deltas() works. 63 | 64 | # Overview: 65 | # --------- 66 | # 1) Creates a custom ticker_updates_container dict. 67 | # 2) Subscribes to N tickers and starts receiving market data. 68 | # 3) When information is received, checks if the ticker is 69 | # in ticker_updates_container and adds it if not. 70 | # 4) Disconnects when it has data information for each ticker. 71 | 72 | from bittrex_websocket.websocket_client import BittrexSocket 73 | from time import sleep 74 | 75 | def main(): 76 | class MySocket(BittrexSocket): 77 | 78 | async def on_public(self, msg): 79 | name = msg['M'] 80 | if name not in ticker_updates_container: 81 | ticker_updates_container[name] = msg 82 | print('Just received market update for {}.'.format(name)) 83 | 84 | # Create container 85 | ticker_updates_container = {} 86 | # Create the socket instance 87 | ws = MySocket() 88 | # Enable logging 89 | ws.enable_log() 90 | # Define tickers 91 | tickers = ['BTC-ETH', 'BTC-NEO', 'BTC-ZEC', 'ETH-NEO', 'ETH-ZEC'] 92 | # Subscribe to ticker information 93 | for ticker in tickers: 94 | sleep(0.01) 95 | ws.subscribe_to_exchange_deltas([ticker]) 96 | 97 | # Users can also subscribe without introducing delays during invoking but 98 | # it is the recommended way when you are subscribing to a large list of tickers. 99 | # ws.subscribe_to_exchange_deltas(tickers) 100 | 101 | while len(ticker_updates_container) < len(tickers): 102 | sleep(1) 103 | else: 104 | print('We have received updates for all tickers. Closing...') 105 | ws.disconnect() 106 | sleep(10) 107 | 108 | if __name__ == "__main__": 109 | main() 110 | 111 | Disclaimer 112 | ---------- 113 | I am not associated with Bittrex. Use the library at your own risk, I don't bear any responsibility if you end up losing your money. 114 | -------------------------------------------------------------------------------- /bittrex_websocket/_abc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # bittrex_websocket/_abc.py 5 | # Stanislav Lazarov 6 | # 7 | # Official Bittrex documentation: https://github.com/Bittrex/beta 8 | 9 | from abc import ABC, abstractmethod 10 | 11 | 12 | class WebSocket(ABC): 13 | 14 | @abstractmethod 15 | def subscribe_to_exchange_deltas(self, tickers): 16 | """ 17 | Allows the caller to receive real-time updates to the state of a SINGLE market. 18 | Upon subscribing, the callback will be invoked with market deltas as they occur. 19 | 20 | This feed only contains updates to exchange state. To form a complete picture of 21 | exchange state, users must first call QueryExchangeState and merge deltas into 22 | the data structure returned in that call. 23 | 24 | :param tickers: A list of tickers you are interested in. 25 | :type tickers: [] 26 | 27 | https://github.com/Bittrex/beta/#subscribetoexchangedeltas 28 | 29 | JSON Payload: 30 | { 31 | MarketName: string, 32 | Nonce: int, 33 | Buys: 34 | [ 35 | { 36 | Type: string - enum(ADD | REMOVE | UPDATE), 37 | Rate: decimal, 38 | Quantity: decimal 39 | } 40 | ], 41 | Sells: 42 | [ 43 | { 44 | Type: string - enum(ADD | REMOVE | UPDATE), 45 | Rate: decimal, 46 | Quantity: decimal 47 | } 48 | ], 49 | Fills: 50 | [ 51 | { 52 | OrderType: string, 53 | Rate: decimal, 54 | Quantity: decimal, 55 | TimeStamp: date 56 | } 57 | ] 58 | } 59 | """ 60 | 61 | @abstractmethod 62 | def subscribe_to_summary_deltas(self): 63 | """ 64 | Allows the caller to receive real-time updates of the state of ALL markets. 65 | Upon subscribing, the callback will be invoked with market deltas as they occur. 66 | 67 | Summary delta callbacks are verbose. A subset of the same data limited to the 68 | market name, the last price, and the base currency volume can be obtained via 69 | `subscribe_to_summary_lite_deltas`. 70 | 71 | https://github.com/Bittrex/beta#subscribetosummarydeltas 72 | 73 | JSON Payload: 74 | { 75 | Nonce : int, 76 | Deltas : 77 | [ 78 | { 79 | MarketName : string, 80 | High : decimal, 81 | Low : decimal, 82 | Volume : decimal, 83 | Last : decimal, 84 | BaseVolume : decimal, 85 | TimeStamp : date, 86 | Bid : decimal, 87 | Ask : decimal, 88 | OpenBuyOrders : int, 89 | OpenSellOrders : int, 90 | PrevDay : decimal, 91 | Created : date 92 | } 93 | ] 94 | } 95 | """ 96 | 97 | @abstractmethod 98 | def subscribe_to_summary_lite_deltas(self): 99 | """ 100 | Similar to `subscribe_to_summary_deltas`. 101 | Shows only market name, last price and base currency volume. 102 | 103 | JSON Payload: 104 | { 105 | Deltas: 106 | [ 107 | { 108 | MarketName: string, 109 | Last: decimal, 110 | BaseVolume: decimal 111 | } 112 | ] 113 | } 114 | """ 115 | 116 | @abstractmethod 117 | def query_summary_state(self): 118 | """ 119 | Allows the caller to retrieve the full state for all markets. 120 | 121 | JSON payload: 122 | { 123 | Nonce: int, 124 | Summaries: 125 | [ 126 | { 127 | MarketName: string, 128 | High: decimal, 129 | Low: decimal, 130 | Volume: decimal, 131 | Last: decimal, 132 | BaseVolume: decimal, 133 | TimeStamp: date, 134 | Bid: decimal, 135 | Ask: decimal, 136 | OpenBuyOrders: int, 137 | OpenSellOrders: int, 138 | PrevDay: decimal, 139 | Created: date 140 | } 141 | ] 142 | } 143 | """ 144 | 145 | @abstractmethod 146 | def query_exchange_state(self, tickers): 147 | """ 148 | Allows the caller to retrieve the full order book for a specific market. 149 | 150 | :param tickers: A list of tickers you are interested in. 151 | :type tickers: [] 152 | 153 | JSON payload: 154 | { 155 | MarketName : string, 156 | Nonce : int, 157 | Buys: 158 | [ 159 | { 160 | Quantity : decimal, 161 | Rate : decimal 162 | } 163 | ], 164 | Sells: 165 | [ 166 | { 167 | Quantity : decimal, 168 | Rate : decimal 169 | } 170 | ], 171 | Fills: 172 | [ 173 | { 174 | Id : int, 175 | TimeStamp : date, 176 | Quantity : decimal, 177 | Price : decimal, 178 | Total : decimal, 179 | FillType : string, 180 | OrderType : string 181 | } 182 | ] 183 | } 184 | """ 185 | 186 | @abstractmethod 187 | def authenticate(self, api_key, api_secret): 188 | """ 189 | Verifies a user’s identity to the server and begins receiving account-level notifications 190 | 191 | :param api_key: Your api_key with the relevant permissions. 192 | :type api_key: str 193 | :param api_secret: Your api_secret with the relevant permissions. 194 | :type api_secret: str 195 | 196 | https://github.com/slazarov/beta#authenticate 197 | """ 198 | 199 | @abstractmethod 200 | async def on_public(self, msg): 201 | """ 202 | Streams all incoming messages from public delta channels. 203 | """ 204 | 205 | @abstractmethod 206 | async def on_private(self, msg): 207 | """ 208 | Streams all incoming messages from private delta channels. 209 | """ 210 | -------------------------------------------------------------------------------- /resources/README_OLD.md: -------------------------------------------------------------------------------- 1 | # bittrex-websocket-aio 2 | Asynchronous Python websocket client for getting live streaming data from [Bittrex Exchange](http://bittrex.com). 3 | 4 | The library is based on asyncio, hence it requires Python>=3.5. 5 | 6 | If you are using lower Python version (2.7 / 3.0 - 3.4) or prefer gevent, try my other library [slazarov/python-bittrex-websocket](https://github.com/slazarov/python-bittrex-websocket) 7 | 8 | # My plans for the websocket client 9 | 10 | Bittrex released their [official beta websocket documentation](https://github.com/Bittrex/beta) on 27-March-2018. 11 | The major changes were the removal of the need to bypass Cloudflare and the introduction of new public (`Lite Summary Delta`) and private (`Balance Delta` & `Order Delta`) channels. 12 | 13 | Following that, I decided to repurpose the client as a higher level Bittrex API which users can use to build on. The major changes, which are going to be reflected both in the aio and soon in the gevent version of the client, will be: 14 | 15 | * ~~Existing methods will be restructured in order to mimic the official ones, i.e `subscribe_to_orderbook_update` will become `subscribe_to_exchange_deltas`. This would make referencing the official documentation more clear and will reduce confusion.~~ 16 | * ~~`QueryExchangeState` will become a public method so that users can invoke it freely.~~ 17 | * The method `subscribe_to_orderbook` will be removed and instead placed as a separate module. Before the latter happens, users can use the legacy library. 18 | * ~~Private, account specific methods will be implemented, i.e `Balance Delta` & `Order Delta`~~ 19 | * ~~Replacement of the legacy `on_channels` with only two channels for the public and private streams.~~ 20 | 21 | ### Disclaimer 22 | 23 | *I am not associated with Bittrex. Use the library at your own risk, I don't bear any responsibility if you end up losing your money.* 24 | 25 | *The code is licensed under the MIT license. Please consider the following message:* 26 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 28 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 29 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 30 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 31 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE 32 | 33 | # What can I use it for? 34 | You can use it for various purposes, some examples include: 35 | * maintaining live order book 36 | * recording trade history 37 | * analysing order flow 38 | 39 | Use your imagination. 40 | 41 | # Notices 42 | 43 | # Road map 44 | 45 | # Dependencies 46 | 47 | # Installation 48 | 49 | The library can be installed through Github and PyPi. For the latest updates, use Github. 50 | 51 | ```python 52 | pip install git+https://github.com/slazarov/python-bittrex-websocket-aio.git 53 | pip install git+https://github.com/slazarov/python-bittrex-websocket-aio.git@next-version-number 54 | pip install bittrex-websocket-aio 55 | ``` 56 | 57 | # Methods 58 | #### Custom URL 59 | Custom URLs can be passed to the client upon instantiating. 60 | ```python 61 | # 'https://socket.bittrex.com/signalr' is currently Cloudflare protected 62 | # 'https://beta.bittrex.com/signalr' (DEFAULT) is not 63 | 64 | # Create the socket instance 65 | ws = MySocket(url=None) 66 | # rest of your code 67 | ``` 68 | #### Subscribe Methods 69 | ```python 70 | def subscribe_to_exchange_deltas(self, tickers): 71 | """ 72 | Allows the caller to receive real-time updates to the state of a SINGLE market. 73 | Upon subscribing, the callback will be invoked with market deltas as they occur. 74 | 75 | This feed only contains updates to exchange state. To form a complete picture of 76 | exchange state, users must first call QueryExchangeState and merge deltas into 77 | the data structure returned in that call. 78 | 79 | :param tickers: A list of tickers you are interested in. 80 | :type tickers: [] 81 | """ 82 | 83 | 84 | def subscribe_to_summary_deltas(self): 85 | """ 86 | Allows the caller to receive real-time updates of the state of ALL markets. 87 | Upon subscribing, the callback will be invoked with market deltas as they occur. 88 | 89 | Summary delta callbacks are verbose. A subset of the same data limited to the 90 | market name, the last price, and the base currency volume can be obtained via 91 | `subscribe_to_summary_lite_deltas`. 92 | 93 | https://github.com/Bittrex/beta#subscribetosummarydeltas 94 | """ 95 | 96 | def subscribe_to_summary_lite_deltas(self): 97 | """ 98 | Similar to `subscribe_to_summary_deltas`. 99 | Shows only market name, last price and base currency volume. 100 | 101 | """ 102 | 103 | def query_summary_state(self): 104 | """ 105 | Allows the caller to retrieve the full state for all markets. 106 | """ 107 | 108 | def query_exchange_state(self, tickers): 109 | """ 110 | Allows the caller to retrieve the full order book for a specific market. 111 | 112 | :param tickers: A list of tickers you are interested in. 113 | :type tickers: [] 114 | """ 115 | 116 | def authenticate(self, api_key, api_secret): 117 | """ 118 | Verifies a user’s identity to the server and begins receiving account-level notifications 119 | 120 | :param api_key: Your api_key with the relevant permissions. 121 | :type api_key: str 122 | :param api_secret: Your api_secret with the relevant permissions. 123 | :type api_secret: str 124 | """ 125 | ``` 126 | 127 | #### Other Methods 128 | 129 | ```python 130 | def disconnect(self): 131 | """ 132 | Disconnects the socket. 133 | """ 134 | 135 | def enable_log(file_name=None): 136 | """ 137 | Enables logging. 138 | 139 | :param file_name: The name of the log file, located in the same directory as the executing script. 140 | :type file_name: str 141 | """ 142 | 143 | def disable_log(): 144 | """ 145 | Disables logging. 146 | """ 147 | ``` 148 | 149 | # Message channels 150 | ```python 151 | async def on_public(self, msg): 152 | # The main channel for all public methods. 153 | 154 | async def on_private(self, msg): 155 | # The main channel for all private methods. 156 | 157 | async def on_error(self, error): 158 | # Receive error message from the SignalR connection. 159 | 160 | ``` 161 | 162 | # Sample usage 163 | Check the examples folder. 164 | 165 | # Change log 166 | 0.0.0.2.5 - 22/04/2018 167 | * Custom urls can now be passed to the client 168 | * If `cfscrape` is installed, the client will automatically use it 169 | 170 | 0.0.2 - 14/04/2018 171 | * Implemented reconnection 172 | * Implemented private account-level methods. Check `authenticate(self, api_key, api_secret)`. 173 | 174 | 0.0.1 - 07/04/2018 175 | * Initial release on github. 176 | 177 | # Other libraries 178 | **[python-bittrex-autosell](https://github.com/slazarov/python-bittrex-autosell)** 179 | 180 | Python CLI tool to auto sell coins on Bittrex. 181 | 182 | It is used in the cases when you want to auto sell a specific coin for another, but there is no direct market, so you have to use an intermediate market. 183 | 184 | **[python-signalr-client-aio](https://github.com/slazarov/python-signalr-client)** 185 | 186 | SignalR client for python based on asyncio 187 | 188 | **[bittrex-websocket](https://github.com/slazarov/python-bittrex-websocket)** 189 | 190 | Python gevent-based websocket client (SignalR) for getting live streaming data from Bittrex Exchange. 191 | -------------------------------------------------------------------------------- /bittrex_websocket/websocket_client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | # bittrex_websocket/websocket_client.py 5 | # Stanislav Lazarov 6 | 7 | import logging 8 | from ._logger import add_stream_logger, remove_stream_logger 9 | from threading import Thread 10 | from ._queue_events import * 11 | from .constants import EventTypes, BittrexParameters, BittrexMethods, ErrorMessages, OtherConstants 12 | from ._auxiliary import process_message, create_signature, BittrexConnection 13 | from ._abc import WebSocket 14 | from queue import Queue 15 | from ._exceptions import * 16 | from signalr_aio import Connection 17 | 18 | try: 19 | from cfscrape import create_scraper as Session 20 | except ImportError: 21 | from requests import Session 22 | 23 | logger = logging.getLogger(__name__) 24 | 25 | 26 | class BittrexSocket(WebSocket): 27 | 28 | def __init__(self, url=None): 29 | self.control_queue = None 30 | self.invokes = [] 31 | self.tickers = None 32 | self.connection = None 33 | self.threads = [] 34 | self.credentials = None 35 | self.url = BittrexParameters.URL if url is None else url 36 | self._start_main_thread() 37 | 38 | def _start_main_thread(self): 39 | self.control_queue = Queue() 40 | self.control_queue.put(ConnectEvent()) 41 | thread = Thread(target=self.control_queue_handler, daemon=True, name='ControlQueueThread') 42 | self.threads.append(thread) 43 | thread.start() 44 | 45 | def control_queue_handler(self): 46 | while True: 47 | event = self.control_queue.get() 48 | if event is not None: 49 | if event.type == EventTypes.CONNECT: 50 | self._handle_connect() 51 | elif event.type == EventTypes.SUBSCRIBE: 52 | self._handle_subscribe(event.invoke, event.payload) 53 | elif event.type == EventTypes.RECONNECT: 54 | self._handle_reconnect(event.error_message) 55 | elif event.type == EventTypes.CLOSE: 56 | self.connection.conn.close() 57 | break 58 | self.control_queue.task_done() 59 | 60 | def _handle_connect(self): 61 | connection = Connection(self.url, Session()) 62 | hub = connection.register_hub(BittrexParameters.HUB) 63 | connection.received += self._on_debug 64 | connection.error += self.on_error 65 | hub.client.on(BittrexParameters.MARKET_DELTA, self._on_public) 66 | hub.client.on(BittrexParameters.SUMMARY_DELTA, self._on_public) 67 | hub.client.on(BittrexParameters.SUMMARY_DELTA_LITE, self._on_public) 68 | hub.client.on(BittrexParameters.BALANCE_DELTA, self._on_private) 69 | hub.client.on(BittrexParameters.ORDER_DELTA, self._on_private) 70 | self.connection = BittrexConnection(connection, hub) 71 | thread = Thread(target=self._connection_handler, daemon=True, name='SocketConnectionThread') 72 | self.threads.append(thread) 73 | thread.start() 74 | 75 | def _connection_handler(self): 76 | if str(type(self.connection.conn.session)) == OtherConstants.CF_SESSION_TYPE: 77 | logger.info('Establishing connection to Bittrex through {}.'.format(self.url)) 78 | logger.info('cfscrape detected, using a cfscrape session instead of requests.') 79 | else: 80 | logger.info('Establishing connection to Bittrex through {}.'.format(self.url)) 81 | try: 82 | self.connection.conn.start() 83 | except ConnectionClosed as e: 84 | if e.code == 1000: 85 | logger.info('Bittrex connection successfully closed.') 86 | elif e.code == 1006: 87 | event = ReconnectEvent(e.args[0]) 88 | self.control_queue.put(event) 89 | except ConnectionError as e: 90 | raise ConnectionError(e) 91 | except InvalidStatusCode as e: 92 | message = "Status code not 101: {}".format(e.status_code) 93 | event = ReconnectEvent(message) 94 | self.control_queue.put(event) 95 | 96 | def _handle_subscribe(self, invoke, payload): 97 | if invoke in [BittrexMethods.SUBSCRIBE_TO_EXCHANGE_DELTAS, BittrexMethods.QUERY_EXCHANGE_STATE]: 98 | for ticker in payload[0]: 99 | self.invokes.append({'invoke': invoke, 'ticker': ticker}) 100 | self.connection.corehub.server.invoke(invoke, ticker) 101 | logger.info('Successfully subscribed to [{}] for [{}].'.format(invoke, ticker)) 102 | elif invoke == BittrexMethods.GET_AUTH_CONTENT: 103 | self.connection.corehub.server.invoke(invoke, payload[0]) 104 | self.invokes.append({'invoke': invoke, 'ticker': payload[0]}) 105 | logger.info('Retrieving authentication challenge.') 106 | elif invoke == BittrexMethods.AUTHENTICATE: 107 | self.connection.corehub.server.invoke(invoke, payload[0], payload[1]) 108 | logger.info('Challenge retrieved. Sending authentication. Awaiting messages...') 109 | # No need to append invoke list, because AUTHENTICATE is called from successful GET_AUTH_CONTENT. 110 | else: 111 | self.invokes.append({'invoke': invoke, 'ticker': None}) 112 | self.connection.corehub.server.invoke(invoke) 113 | logger.info('Successfully invoked [{}].'.format(invoke)) 114 | 115 | def _handle_reconnect(self, error_message): 116 | logger.error('{}.'.format(error_message)) 117 | logger.error('Initiating reconnection procedure') 118 | events = [] 119 | for item in self.invokes: 120 | event = SubscribeEvent(item['invoke'], [item['ticker']]) 121 | events.append(event) 122 | # Reset previous connection 123 | self.invokes, self.connection = [], None 124 | # Restart 125 | self.control_queue.put(ConnectEvent()) 126 | for event in events: 127 | self.control_queue.put(event) 128 | 129 | # ============== 130 | # Public Methods 131 | # ============== 132 | 133 | def subscribe_to_exchange_deltas(self, tickers): 134 | if type(tickers) is list: 135 | invoke = BittrexMethods.SUBSCRIBE_TO_EXCHANGE_DELTAS 136 | event = SubscribeEvent(invoke, tickers) 137 | self.control_queue.put(event) 138 | else: 139 | raise TypeError(ErrorMessages.INVALID_TICKER_INPUT) 140 | 141 | def subscribe_to_summary_deltas(self): 142 | invoke = BittrexMethods.SUBSCRIBE_TO_SUMMARY_DELTAS 143 | event = SubscribeEvent(invoke, None) 144 | self.control_queue.put(event) 145 | 146 | def subscribe_to_summary_lite_deltas(self): 147 | invoke = BittrexMethods.SUBSCRIBE_TO_SUMMARY_LITE_DELTAS 148 | event = SubscribeEvent(invoke, None) 149 | self.control_queue.put(event) 150 | 151 | def query_summary_state(self): 152 | invoke = BittrexMethods.QUERY_SUMMARY_STATE 153 | event = SubscribeEvent(invoke, None) 154 | self.control_queue.put(event) 155 | 156 | def query_exchange_state(self, tickers): 157 | if type(tickers) is list: 158 | invoke = BittrexMethods.QUERY_EXCHANGE_STATE 159 | event = SubscribeEvent(invoke, tickers) 160 | self.control_queue.put(event) 161 | else: 162 | raise TypeError(ErrorMessages.INVALID_TICKER_INPUT) 163 | 164 | def authenticate(self, api_key, api_secret): 165 | self.credentials = {'api_key': api_key, 'api_secret': api_secret} 166 | event = SubscribeEvent(BittrexMethods.GET_AUTH_CONTENT, api_key) 167 | self.control_queue.put(event) 168 | 169 | def disconnect(self): 170 | self.control_queue.put(CloseEvent()) 171 | 172 | # ======================= 173 | # Private Channel Methods 174 | # ======================= 175 | 176 | async def _on_public(self, args): 177 | msg = await process_message(args[0]) 178 | if 'D' in msg: 179 | if len(msg['D'][0]) > 3: 180 | msg['invoke_type'] = BittrexMethods.SUBSCRIBE_TO_SUMMARY_DELTAS 181 | else: 182 | msg['invoke_type'] = BittrexMethods.SUBSCRIBE_TO_SUMMARY_LITE_DELTAS 183 | else: 184 | msg['invoke_type'] = BittrexMethods.SUBSCRIBE_TO_EXCHANGE_DELTAS 185 | await self.on_public(msg) 186 | 187 | async def _on_private(self, args): 188 | msg = await process_message(args[0]) 189 | await self.on_private(msg) 190 | 191 | async def _on_debug(self, **kwargs): 192 | # `QueryExchangeState`, `QuerySummaryState` and `GetAuthContext` are received in the debug channel. 193 | await self._is_query_invoke(kwargs) 194 | 195 | async def _is_query_invoke(self, kwargs): 196 | if 'R' in kwargs and type(kwargs['R']) is not bool: 197 | invoke = self.invokes[int(kwargs['I'])]['invoke'] 198 | if invoke == BittrexMethods.GET_AUTH_CONTENT: 199 | signature = await create_signature(self.credentials['api_secret'], kwargs['R']) 200 | event = SubscribeEvent(BittrexMethods.AUTHENTICATE, self.credentials['api_key'], signature) 201 | self.control_queue.put(event) 202 | else: 203 | msg = await process_message(kwargs['R']) 204 | if msg is not None: 205 | msg['invoke_type'] = invoke 206 | msg['ticker'] = self.invokes[int(kwargs['I'])].get('ticker') 207 | await self.on_public(msg) 208 | 209 | # ====================== 210 | # Public Channel Methods 211 | # ====================== 212 | 213 | async def on_public(self, msg): 214 | pass 215 | 216 | async def on_private(self, msg): 217 | pass 218 | 219 | async def on_error(self, args): 220 | logger.error(args) 221 | 222 | # ============= 223 | # Other Methods 224 | # ============= 225 | 226 | @staticmethod 227 | def enable_log(file_name=None): 228 | """ 229 | Enables logging. 230 | :param file_name: The name of the log file, located in the same directory as the executing script. 231 | :type file_name: str 232 | """ 233 | add_stream_logger(file_name=file_name) 234 | 235 | @staticmethod 236 | def disable_log(): 237 | """ 238 | Disables logging. 239 | """ 240 | remove_stream_logger() 241 | --------------------------------------------------------------------------------