├── __init__.py ├── wazirx_sapi_client ├── __init__.py ├── rest │ ├── __init__.py │ ├── endpoints.py │ ├── test.py │ ├── api_mapper.json │ └── client.py └── websocket │ ├── __init__.py │ ├── test.py │ └── websocket_client.py ├── requirements.txt ├── LICENSE.txt ├── .gitignore └── README.md /__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /wazirx_sapi_client/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | websockets -------------------------------------------------------------------------------- /wazirx_sapi_client/rest/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if int(sys.version[0]) < 3 or int(sys.version[2]) < 7: 4 | raise BaseException("Python>=3.7 required") 5 | 6 | from wazirx_sapi_client.rest.client import Client 7 | -------------------------------------------------------------------------------- /wazirx_sapi_client/websocket/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | if int(sys.version[0]) < 3 or int(sys.version[2]) < 7: 4 | raise BaseException("Python>=3.7 required") 5 | 6 | from wazirx_sapi_client.websocket.websocket_client import WebsocketClient 7 | -------------------------------------------------------------------------------- /wazirx_sapi_client/rest/endpoints.py: -------------------------------------------------------------------------------- 1 | ENDPOINTS = { 2 | # Public API Endpoints 3 | "ping": 'v1/ping', 4 | "time": 'v1/time', 5 | "system_status": 'v1/systemStatus', 6 | "exchange_info": 'v1/exchangeInfo', 7 | "tickers": 'v1/tickers/24hr', 8 | "ticker": 'v1/ticker/24hr', 9 | "depth": 'v1/depth', 10 | "trades": 'v1/trades', 11 | "historical_trades": 'v1/historicalTrades', 12 | 13 | # Account API Endpoints 14 | "order": 'v1/order', 15 | "test_order": 'v1/order/test', 16 | "open_orders": 'v1/openOrders', 17 | "all_orders": 'v1/allOrders', 18 | "account": 'v1/account', 19 | "funds": 'v1/funds', 20 | 21 | # WebSocket-Client 22 | "create_auth_token": "v1/create_auth_token" 23 | 24 | } 25 | -------------------------------------------------------------------------------- /wazirx_sapi_client/rest/test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | 5 | PATH_TO_ADD = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) 6 | if PATH_TO_ADD not in sys.path: 7 | sys.path.append(PATH_TO_ADD) 8 | 9 | from wazirx_sapi_client.rest import Client 10 | 11 | # Keys for private events 12 | api_key = "test_api_key" 13 | secret_key = "test_secret_key" 14 | 15 | # public 16 | c = Client() 17 | print(c.send("ping")) 18 | print(c.send("time")) 19 | print(c.send("system_status")) 20 | print(c.send("exchange_info")) 21 | 22 | # private 23 | c = Client(api_key=api_key, secret_key=secret_key) 24 | print(c.send("historical_trades", 25 | {"limit": 10, "symbol": "btcinr", "recvWindow": 10000, "timestamp": int(time.time() * 1000)} 26 | )) 27 | print(c.send('create_order', 28 | {"symbol": "btcinr", "side": "buy", "type": "limit", "price": 500, "quantity": 1, "recvWindow": 10000, 29 | "timestamp": int(time.time() * 1000)})) 30 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Wazirx 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /wazirx_sapi_client/websocket/test.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import sys, os 3 | 4 | PATH_TO_ADD = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) 5 | if PATH_TO_ADD not in sys.path: 6 | sys.path.append(PATH_TO_ADD) 7 | 8 | from wazirx_sapi_client.websocket import WebsocketClient 9 | 10 | 11 | async def main(): 12 | """ 13 | For public streams, api_key, secret_key is not required i.e. 14 | ws_client = WebsocketClient() 15 | For private streams, api_key, secret_key are required while initialising WebsocketClient i.e. 16 | ws_client = WebsocketClient(api_key=api_key, secret_key=secret_key) 17 | 18 | """ 19 | # Keys for private events 20 | api_key = "test_api_key" 21 | secret_key = "test_secret_key" 22 | 23 | ws_client = WebsocketClient(api_key=api_key, secret_key=secret_key) 24 | 25 | asyncio.create_task( 26 | ws_client.connect( 27 | ) 28 | ) 29 | await ws_client.trades(symbol=['wrxinr','btcinr']) 30 | await ws_client.depth(symbol=['wrxinr','btcinr']) 31 | await ws_client.user_stream(streams=['orderUpdate', 'ownTrade', 'outboundAccountPosition']) 32 | await ws_client.multi_stream(streams=[{'symbol': ['wrxinr','btcinr'], 'type': 'depth'}, {'symbol':['wrxinr','btcinr'], 'type':'trades'}, {'type':'ticker'}]) 33 | 34 | 35 | if __name__ == "__main__": 36 | loop = asyncio.get_event_loop() 37 | loop.create_task(main()) 38 | loop.run_forever() 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | .idea/* 35 | *.DS_Store 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 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | 88 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 89 | __pypackages__/ 90 | 91 | # Celery stuff 92 | celerybeat-schedule 93 | celerybeat.pid 94 | 95 | # SageMath parsed files 96 | *.sage.py 97 | 98 | # Environments 99 | .env 100 | .venv 101 | env/ 102 | venv/ 103 | ENV/ 104 | env.bak/ 105 | venv.bak/ 106 | 107 | # Spyder project settings 108 | .spyderproject 109 | .spyproject 110 | 111 | # Rope project settings 112 | .ropeproject 113 | 114 | # mkdocs documentation 115 | /site 116 | 117 | # mypy 118 | .mypy_cache/ 119 | .dmypy.json 120 | dmypy.json 121 | 122 | # Pyre type checker 123 | .pyre/ 124 | 125 | # pytype static type analyzer 126 | .pytype/ 127 | 128 | # Cython debug symbols 129 | cython_debug/ 130 | 131 | -------------------------------------------------------------------------------- /wazirx_sapi_client/rest/api_mapper.json: -------------------------------------------------------------------------------- 1 | { 2 | "ping": { 3 | "client": "public", 4 | "action": "get", 5 | "endpoint": "ping" 6 | }, 7 | "time": { 8 | "client": "public", 9 | "action": "get", 10 | "endpoint": "time" 11 | }, 12 | "system_status": { 13 | "client": "public", 14 | "action": "get", 15 | "endpoint": "time" 16 | }, 17 | "exchange_info": { 18 | "client": "public", 19 | "action": "get", 20 | "endpoint": "exchange_info" 21 | }, 22 | "tickers": { 23 | "client": "public", 24 | "action": "get", 25 | "endpoint": "tickers" 26 | }, 27 | "ticker": { 28 | "client": "public", 29 | "action": "get", 30 | "endpoint": "ticker" 31 | }, 32 | "depth": { 33 | "client": "public", 34 | "action": "get", 35 | "endpoint": "depth" 36 | }, 37 | "trades": { 38 | "client": "public", 39 | "action": "get", 40 | "endpoint": "trades" 41 | }, 42 | "historical_trades": { 43 | "client": "signed", 44 | "action": "get", 45 | "endpoint": "historical_trades" 46 | }, 47 | "create_order": { 48 | "client": "signed", 49 | "action": "post", 50 | "endpoint": "order" 51 | }, 52 | "create_test_order": { 53 | "client": "signed", 54 | "action": "post", 55 | "endpoint": "test_order" 56 | }, 57 | "query_order": { 58 | "client": "signed", 59 | "action": "get", 60 | "endpoint": "order" 61 | }, 62 | "cancel_order": { 63 | "client": "signed", 64 | "action": "delete", 65 | "endpoint": "order" 66 | }, 67 | "open_orders": { 68 | "client": "signed", 69 | "action": "get", 70 | "endpoint": "open_orders" 71 | }, 72 | "cancel_open_orders": { 73 | "client": "signed", 74 | "action": "delete", 75 | "endpoint": "open_orders" 76 | }, 77 | "all_orders": { 78 | "client": "signed", 79 | "action": "get", 80 | "endpoint": "all_orders" 81 | }, 82 | "account_info": { 83 | "client": "signed", 84 | "action": "get", 85 | "endpoint": "account" 86 | }, 87 | "funds_info": { 88 | "client": "signed", 89 | "action": "get", 90 | "endpoint": "funds" 91 | }, 92 | "create_auth_token": { 93 | "client": "signed", 94 | "action": "post", 95 | "endpoint": "create_auth_token" 96 | } 97 | } -------------------------------------------------------------------------------- /wazirx_sapi_client/rest/client.py: -------------------------------------------------------------------------------- 1 | import hashlib, collections 2 | import hmac 3 | import json 4 | import os 5 | import urllib 6 | 7 | import requests 8 | 9 | from wazirx_sapi_client.rest.endpoints import ENDPOINTS 10 | 11 | PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) 12 | 13 | 14 | class BaseClient(object): 15 | API_URL = 'https://api.wazirx.com/sapi/' 16 | 17 | def __init__( 18 | self, api_key="", secret_key="" 19 | ): 20 | self.api_key = api_key 21 | self.secret_key = secret_key 22 | self.api_mapper = json.load(open(PROJECT_ROOT + "/api_mapper.json", "r")) 23 | 24 | 25 | class Client(BaseClient): 26 | def __init__( 27 | self, api_key="", secret_key="" 28 | ): 29 | super(Client, self).__init__(api_key, secret_key) 30 | 31 | def send(self, name="", kwargs=None): 32 | if kwargs is None: 33 | kwargs = {} 34 | if not all([name, self.api_mapper.get(name, "")]): 35 | raise BaseException("Valid Api Name Required") 36 | 37 | api_detail = self.api_mapper[name] 38 | return self._send_request(api_detail, kwargs) 39 | 40 | def _send_request(self, api_detail, kwargs): 41 | headers = self._get_headers(api_detail) 42 | if api_detail.get("client", "") == "signed": 43 | kwargs = collections.OrderedDict(sorted(kwargs.items(), key=lambda x: x[0])) 44 | kwargs["signature"] = self._get_signature(api_detail, kwargs) 45 | 46 | request_method = api_detail["action"].lower() 47 | url = self.API_URL + ENDPOINTS[api_detail["endpoint"]] 48 | response = None 49 | if request_method == "get": 50 | response = requests.get(url, params=kwargs, headers=headers) 51 | elif request_method == "post": 52 | response = requests.post(url, data=kwargs, headers=headers) 53 | elif request_method == "delete": 54 | response = requests.delete(url, data=kwargs, headers=headers) 55 | if response is not None: 56 | return response.status_code, response.json() 57 | raise BaseException("Invalid Request Type") 58 | 59 | def _get_headers(self, api_detail): 60 | output = { 61 | "Content-Type": "application/x-www-form-urlencoded", 62 | } 63 | if api_detail.get("client", "") == "signed": 64 | output["X-Api-Key"] = self.api_key 65 | 66 | return output 67 | 68 | def _get_signature(self, api_detail, kwargs={}): 69 | sign_payload = urllib.parse.urlencode(kwargs) 70 | signature = hmac.new(bytes(self.secret_key, 'latin-1'), msg=bytes(sign_payload, 'latin-1'), 71 | digestmod=hashlib.sha256).hexdigest() 72 | return signature 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wazirx Python 2 | 3 | [![Pyhton](https://img.shields.io/badge/python-3-green)](https://docs.wazirx.com) 4 | 5 | This is an official Python wrapper for the Wazirx exchange REST and WebSocket APIs. 6 | 7 | ##### Notice 8 | 9 | We are now at 1.0 and there may be things breaking, don't hesitate to raise an issue if you feel so! 10 | 11 | ## Installation 12 | Generate **API KEY** and **Secret Key** from Wazirx website [here](https://wazirx.com/settings/keys) 13 | 14 | ```python 15 | pip3 install -r requirements.txt 16 | ``` 17 | 18 | ## Features 19 | 20 | #### Current 21 | 22 | * Basic implementation of REST API 23 | * Easy to use authentication 24 | * Methods return parsed JSON 25 | * No need to generate timestamps 26 | * No need to generate signatures 27 | * Basic implementation of WebSocket API 28 | * Create task using `asyncio` inside async main function 29 | * Single and multiple streams supported 30 | * All methods runs with `await` call inside async function 31 | 32 | #### Planned 33 | 34 | * Exception handling with responses 35 | * High level abstraction 36 | 37 | ## Getting Started 38 | 39 | #### REST Client 40 | 41 | Import Wazirx Client for Rest: 42 | 43 | ```python 44 | from wazirx_sapi_client.rest import Client 45 | ``` 46 | 47 | Create a new instance of the REST Client: 48 | 49 | ```python 50 | # If you only plan on touching public API endpoints, you can forgo any arguments 51 | client = Client() 52 | # Otherwise provide an api_key and secret_key as keyword arguments 53 | client = Client(api_key='x', secret_key='y') 54 | ``` 55 | 56 | Create various requests: 57 | 58 | ## General Endpoints 59 | 60 | #### Ping 61 | 62 | ```python 63 | client.send("ping") 64 | ``` 65 | Response: 66 | ```json-doc 67 | {} 68 | ``` 69 | #### Server time 70 | 71 | ```python 72 | client.send("time") 73 | ``` 74 | Response: 75 | ```json-doc 76 | { 77 | "serverTime": 1632375945160 78 | } 79 | ``` 80 | #### System status 81 | 82 | ```python 83 | client.send("system_status") 84 | ``` 85 | Response: 86 | ```json-doc 87 | { 88 | "status": "normal", 89 | "message": "System is running normally." 90 | } 91 | ``` 92 | #### Exchange info 93 | 94 | ```python 95 | client.send("exchange_info") 96 | ``` 97 | Response: 98 | ```json-doc 99 | { 100 | "timezone": "UTC", 101 | "serverTime": 1632376074413, 102 | "symbols": [ 103 | { 104 | "symbol": "wrxinr", 105 | "status": "trading", 106 | "baseAsset": "wrx", 107 | "quoteAsset": "inr", 108 | "baseAssetPrecision": 5, 109 | "quoteAssetPrecision": 0, 110 | "orderTypes": [ 111 | "limit", 112 | "stop_limit" 113 | ], 114 | "isSpotTradingAllowed": true, 115 | "filters": [ 116 | { 117 | "filterType": "PRICE_FILTER", 118 | "minPrice": "1", 119 | "tickSize": "1" 120 | } 121 | ] 122 | } 123 | ] 124 | } 125 | ``` 126 | #### Create an order 127 | ```python 128 | client.send("create_order", {"symbol": 'btcinr', "side": 'buy', "type": 'limit', 129 | "quantity": 100, "price": 0.00055, "recvWindow": 1000}) 130 | ``` 131 | Response: 132 | ```json-doc 133 | {"id"=>27007862, "symbol"=>"btcinr", "type"=>"limit", "side"=>"buy", 134 | "status"=>"wait", "price"=>"210.0", "origQty"=>"2.0", "executedQty"=>"0.0", 135 | "createdTime"=>1632310960000, "updatedTime"=>1632310960000} 136 | ``` 137 | ##### For other api methods follow [this](https://github.com/WazirX/wazirx-connector-python/blob/master/wazirx_sapi_client/rest/api_mapper.json). 138 | 139 | ##### For example and better understanding the api client usage refer [here](https://github.com/WazirX/wazirx-connector-python/blob/master/wazirx_sapi_client/rest/test.py) 140 | 141 | Required and optional parameters, as well as enum values, can be found on the [Wazirx Documentation](https://docs.wazirx.com). Parameters should always be passed to client methods as keyword arguments in snake_case form. 142 | 143 | #### WebSocket Client 144 | 145 | Import Wazirx Client for WebSocket and [AsyncIO](https://docs.python.org/3/library/asyncio.html) 146 | 147 | ```python 148 | import asyncio 149 | from wazirx_sapi_client.websocket import WebsocketClient 150 | ``` 151 | 152 | Create a new instance of the REST Client: 153 | 154 | ```python 155 | # If you only plan on touching public API endpoints, you can forgo any arguments 156 | ws = WebsocketClient() 157 | # Otherwise provide an api_key and secret_key as keyword arguments 158 | ws = WebsocketClient(api_key='x', secret_key='y') 159 | ``` 160 | 161 | Create a connection with the websocket using: 162 | 163 | ```python 164 | asyncio.create_task( 165 | ws.connect() 166 | ) 167 | ``` 168 | 169 | Create various WebSocket streams with `await` 170 | 171 | ```python 172 | # Pass the symbol/symbols to subscribe to trades 173 | await ws.trades(symbol=['btcinr','wrxinr'], id=0, action='subscribe') 174 | 175 | # Pass the symbol/symbols to subscribe to depth 176 | await ws.depth(symbol=['btcinr','wrxinr'], id=0, action='subscribe') 177 | 178 | # For all market tickers 179 | await ws.all_market_ticker(id=0, action='subscribe') 180 | ``` 181 | 182 | ##### Note: 183 | * `symbol` can be `Array` for multiple symbols. 184 | * `id` by default is `0`, for unique identification any positive integer can be used. 185 | * `action` only needs to pass in case of `unsubscribe`, default is `subscribe` if no data passed. 186 | #### User Data Stream 187 | 188 | User data streams utilize both the REST and WebSocket APIs. 189 | 190 | Request a listen key from the REST API, and then create a WebSocket stream using it. 191 | 192 | ```python 193 | await ws.user_stream(streams=['orderUpdate', 'ownTrade', 'outboundAccountPosition'], id=0, action='subscribe') 194 | ``` 195 | 196 | To make sure that websocket stays live add the given below code for your `main`. 197 | 198 | ```python 199 | loop = asyncio.get_event_loop() 200 | loop.create_task(main()) 201 | loop.run_forever() 202 | ``` 203 | 204 | ##### For other websocket methods follow [this](https://github.com/WazirX/wazirx-connector-python/blob/master/wazirx_sapi_client/websocket/websocket_client.py). 205 | 206 | ##### For example and better understanding the websocket client usage refer [here](https://github.com/WazirX/wazirx-connector-python/blob/master/wazirx_sapi_client/websocket/test.py) 207 | 208 | ## Development 209 | 210 | After checking out the repo, run `python3 wazirx_sapi_client/rest/test.py` or `python3 wazirx_sapi_client/websocket/test.py` to run the rest apis or websocket tests or experiments respectively. 211 | 212 | ## Contributing 213 | 214 | Bug reports and pull requests are welcome on GitHub at [Issues](https://github.com/WazirX/wazirx-connector-python/issues). 215 | 216 | ## License 217 | 218 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 219 | -------------------------------------------------------------------------------- /wazirx_sapi_client/websocket/websocket_client.py: -------------------------------------------------------------------------------- 1 | """WazirX websockets""" 2 | import asyncio 3 | import json, sys, os, time 4 | import socket, threading 5 | 6 | import websockets 7 | 8 | PATH_TO_ADD = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) 9 | if PATH_TO_ADD not in sys.path: 10 | sys.path.append(PATH_TO_ADD) 11 | from wazirx_sapi_client.rest import Client 12 | 13 | 14 | class BaseWebsocketClient: 15 | """Wazirx Websocket client implementation""" 16 | 17 | def __init__(self, api_key="", secret_key=""): 18 | """ 19 | Initialize the object. 20 | Arguments: 21 | """ 22 | self.api_key = api_key 23 | self.secret_key = secret_key 24 | self.auth_key = "" 25 | self.connections = {"websocket": None} 26 | self.ping_started = False 27 | 28 | 29 | class WebsocketClient(BaseWebsocketClient): 30 | def __init__(self, api_key="", secret_key=""): 31 | super(WebsocketClient, self).__init__(api_key, secret_key) 32 | 33 | def get_auth_token(self): 34 | rest_client = Client(self.api_key, self.secret_key) 35 | status_code, response = rest_client.send("create_auth_token", 36 | {"recvWindow": 10000, "timestamp": int(time.time() * 1000)}) 37 | if status_code == 200: 38 | self.auth_key = response["auth_key"] 39 | return self.auth_key 40 | 41 | async def connect(self, uri="wss://stream.wazirx.com/stream"): 42 | websocket = await websockets.connect(uri=uri) 43 | self.connections = dict() 44 | self.connections["websocket"] = websocket 45 | self.connections["subscriptions"] = [] 46 | _thread = threading.Thread(target=asyncio.run, args=(self.send_heartbeat(),)) 47 | _thread.start() 48 | while True: 49 | try: 50 | message = await websocket.recv() 51 | if "errorMessage" in message: 52 | error = json.loads(message) 53 | print(error) 54 | else: 55 | data = json.loads(message) 56 | print(data) 57 | except socket.gaierror: 58 | print("Socket gaia error") 59 | return 60 | except websockets.ConnectionClosedError: 61 | print("WebSockets connection closed error") 62 | return 63 | except websockets.ConnectionClosedOK: 64 | print("WebSockets connection closed") 65 | return 66 | except ConnectionResetError: 67 | print("Connection reset error") 68 | return 69 | 70 | async def send_heartbeat(self, *args): 71 | while True: 72 | await self.connections["websocket"].send(json.dumps({'event': 'ping'})) 73 | time.sleep(15 * 60) 74 | 75 | async def disconnect(self): 76 | if self.connections["websocket"] is not None: 77 | try: 78 | await self.connections["websocket"].close() 79 | except socket.gaierror: 80 | print("Socket gaia error, let's disconnect anyway...") 81 | except websockets.ConnectionClosedError: 82 | print("WebSockets connection closed error, let's disconnect anyway...") 83 | except websockets.ConnectionClosedOK: 84 | print("WebSockets connection closed ok, let's disconnect anyway...") 85 | except ConnectionResetError: 86 | print("Connection reset error, let's disconnect anyway...") 87 | del self.connections 88 | 89 | async def _send(self, data: dict) -> None: 90 | while not self.connections["websocket"]: 91 | await asyncio.sleep(0.1) 92 | try: 93 | await self.connections["websocket"].send(json.dumps(data)) 94 | except socket.gaierror: 95 | print("Socket gaia error, message not sent...") 96 | except websockets.ConnectionClosedError: 97 | print("WebSockets connection closed error, message not sent...") 98 | except websockets.ConnectionClosedOK: 99 | print("WebSockets connection closed ok, message not sent...") 100 | except ConnectionResetError: 101 | print("Connection reset error, message not sent...") 102 | 103 | async def _sub_unsub( 104 | self, 105 | event, 106 | subscription, 107 | id=0 108 | ): 109 | data = { 110 | "event": event, 111 | "streams": subscription, 112 | } 113 | if self.auth_key: 114 | data["auth_key"] = self.auth_key 115 | if id: 116 | data["id"] = id 117 | 118 | await self._send(data=data) 119 | 120 | async def subscribe( 121 | self, 122 | events=None, 123 | id=0 124 | ): 125 | if events is None: 126 | events = [] 127 | if all([self.api_key, self.secret_key]) and (not self.auth_key): 128 | self.get_auth_token() 129 | await self._sub_unsub( 130 | event="subscribe", 131 | subscription=events, 132 | id=id 133 | ) 134 | 135 | async def trades(self, symbol=[], id=0, action="subscribe"): 136 | events = [] 137 | if symbol: 138 | for s in symbol: 139 | events.append(s+"@trades") 140 | await self._sub_unsub(event=action, subscription=events, id=id) 141 | 142 | async def depth(self, symbol=[], id=0, action="subscribe"): 143 | events = [] 144 | if symbol: 145 | for s in symbol: 146 | events.append(s+"@depth") 147 | await self._sub_unsub(event=action, subscription=events, id=id) 148 | 149 | async def all_market_ticker(self, id=0, action="subscribe"): 150 | events = ["!ticker@arr"] 151 | await self._sub_unsub(event=action, subscription=events, id=id) 152 | 153 | async def user_stream(self, streams=[], id=0, action="subscribe"): 154 | if all([self.api_key, self.secret_key]) and (not self.auth_key): 155 | self.get_auth_token() 156 | await self._sub_unsub(event=action, subscription=streams, id=id) 157 | 158 | async def multi_stream(self, streams=[], id=0, action="subscribe"): 159 | if all([self.api_key, self.secret_key]) and (not self.auth_key): 160 | self.get_auth_token() 161 | format_streams = [] 162 | for stream in streams: 163 | if stream['type'] == 'ticker': 164 | format_streams.append('!ticker@arr') 165 | if stream['type'] == 'depth' or stream['type'] == 'trades': 166 | format_streams += self.get_mapped_streams(symbols=stream['symbol'], type=stream['type']) 167 | await self._sub_unsub(event=action, subscription=format_streams, id=id) 168 | 169 | async def unsubscribe( 170 | self, 171 | events=[] 172 | 173 | ): 174 | await self._sub_unsub( 175 | event="unsubscribe", 176 | subscription=events, 177 | 178 | ) 179 | def get_mapped_streams(self, symbols=[], type=""): 180 | events = [] 181 | for s in symbols: 182 | events.append(s+"@"+type) 183 | return events 184 | --------------------------------------------------------------------------------