From a92928751e58be007682418d2506703a2abf1820 Mon Sep 17 00:00:00 2001 From: Oleg Nedbaylo Date: Fri, 5 Jun 2020 12:02:52 +0200 Subject: [PATCH 1/3] get_currencies and get_instruments were added callback added to the send methods --- ssc2ce/deribit.py | 196 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 166 insertions(+), 30 deletions(-) diff --git a/ssc2ce/deribit.py b/ssc2ce/deribit.py index 8f405f9..7d24c06 100644 --- a/ssc2ce/deribit.py +++ b/ssc2ce/deribit.py @@ -74,10 +74,15 @@ def __init__(self, ] async def run_receiver(self): + """ + Establish a connection and start the receiver loop. + :return: + """ self.ws = await self._session.ws_connect(self.ws_api) if self.on_connect_ws: await self.on_connect_ws() + # A receiver loop while self.ws and not self.ws.closed: message = await self.ws.receive() self.receipt_time = time() @@ -96,19 +101,49 @@ async def run_receiver(self): if self.on_message: await self.on_message(message) - async def send_public(self, request): + def close(self): + super()._close() + + async def stop(self): + """ + Close connection and break the receiver loop + :return: + """ + await self.ws.close() + + async def send_public(self, request: dict, callback=None) -> int: + """ + Send a public request + + :param request: Request without jsonrpc and id fields + :param callback: The function that will be called after receiving the query result. Default is None + :return: Request Id + """ request_id = self.get_id() request = { "jsonrpc": "2.0", "id": request_id, **request } - self.requests[request_id] = request self.logger.info(f"sending:{repr(hide_secret(request))}") await self.ws.send_json(request) + + if callback: + request["callback"] = callback + + self.requests[request_id] = request + return request_id - async def send_private(self, request): + async def send_private(self, request: dict, callback=None) -> int: + """ + Send a private request + + :param request: Request without jsonrpc and id fields + :param callback: The function that will be called after receiving the query result. Default is None + :return: Request Id + """ + request_id = self.get_id() access_token = self.auth_params["access_token"] request["params"]["access_token"] = access_token @@ -118,21 +153,38 @@ async def send_private(self, request): "id": request_id, **request } - self.requests[request_id] = request self.logger.info(f"sending:{repr(hide_secret(request))}") await self.ws.send_json(request) + + if callback: + request["callback"] = callback + + self.requests[request_id] = request + return request_id - async def send(self, request): + async def send(self, request: dict, callback=None) -> int: + """ + A wrapper for send_private and send_public, defines the type of request by content. + + :param request: Request without jsonrpc and id fields + :param callback: The function that will be called after receiving the query result. Default is None + :return: Request Id + """ method = request["method"] if method.starts("public/"): - request_id = await self.send_public(request) + request_id = await self.send_public(request, callback) else: - request_id = await self.send_private(request) + request_id = await self.send_private(request, callback) return request_id - async def auth_login(self): + async def auth_login(self) -> int: + """ + Send an authentication request using parameters stored in the constructor + + :return: Reuest id + """ self.auth_type = AuthType.CREDENTIALS msg = { @@ -150,8 +202,9 @@ async def auth_login(self): request_id = await self.send_public(msg) return request_id - async def auth_client_credentials(self, client_id, client_secret, scope: str = None): + async def auth_client_credentials(self, client_id, client_secret, scope: str = None) -> int: """ + Send a credentials authentication request :param client_id: using the access key :param client_secret: and access secret that can be found on the API page on the website @@ -160,7 +213,7 @@ async def auth_client_credentials(self, client_id, client_secret, scope: str = N trade:[read, read_write, none], wallet:[read, read_write, none], account:[read, read_write, none] - :return: + :return: Request Id """ self.auth_type = AuthType.CREDENTIALS @@ -183,7 +236,20 @@ async def auth_client_credentials(self, client_id, client_secret, scope: str = N request_id = await self.send_public(msg) return request_id - async def auth_password(self, username, password, scope: str = None): + async def auth_password(self, username: str, password: str, scope: str = None) -> int: + """ + Send a password authentication request + + :param username: User name + :param password: Password + :param scope: + connection, session, session:name, + trade:[read, read_write, none], + wallet:[read, read_write, none], + account:[read, read_write, none] + :return: Request Id + """ + self.auth_type = AuthType.PASSWORD msg = { "method": "public/auth", @@ -203,7 +269,12 @@ async def auth_password(self, username, password, scope: str = None): request_id = await self.send_public(msg) return request_id - async def auth_refresh_token(self): + async def auth_refresh_token(self) -> int: + """ + Send a refresh token request + + :return: Request Id + """ msg = { "method": "public/auth", "params": { @@ -215,7 +286,12 @@ async def auth_refresh_token(self): request_id = await self.send_public(msg) return request_id - async def auth_logout(self): + async def auth_logout(self) -> int: + """ + Send a logout request + + :return: Request Id + """ msg = { "method": "private/logout", "params": {} @@ -224,15 +300,54 @@ async def auth_logout(self): request_id = await self.send_private(msg) return request_id - async def set_heartbeat(self, interval: int = 15): + async def set_heartbeat(self, interval: int = 15) -> int: + """ + + :param interval: + :return: + """ request_id = await self.send_public({"method": "public/set_heartbeat", "params": {"interval": interval}}) return request_id - async def disable_heartbeat(self): + async def disable_heartbeat(self) -> int: + """ + + :return: + """ request_id = await self.send_public({"method": "public/disable_heartbeat", "params": {}}) return request_id - async def handle_message(self, message: aiohttp.WSMessage): + async def get_currencies(self, callback=None): + """ + Send a request for a list of cryptocurrencies supported by the API + :param callback: + :return: Request Id + """ + return await self.send_public(request=dict(method="public/get_currencies", params={}), callback=callback) + + async def get_instruments(self, currency: str, kind: str = None, callback=None) -> int: + """ + Send a request for a list available trading instruments + :param currency: The currency symbol: BTC or ETH + :param kind: Instrument kind: future or option, if not provided instruments of all kinds are considered + :param callback: + :return: Request Id + """ + request = {"method": "public/get_instruments", + "params": { + "currency": currency + }} + if kind: + request["kind"] = kind + + return await self.send_public(request=request, callback=callback) + + async def handle_message(self, message: aiohttp.WSMessage) -> None: + """ + + :param message: + :return: + """ if message.type == aiohttp.WSMsgType.TEXT: data = message.json() self.logger.debug(f"handling:{repr(hide_secret(data))}") @@ -250,7 +365,11 @@ async def handle_message(self, message: aiohttp.WSMessage): request_id = data["id"] request = self.requests.get(request_id) if request: - await self.handle_response(request=request, response=data) + if "callback" in request: + callback = request["callback"] + await callback(data) + else: + await self.handle_response(request=request, response=data) del self.requests[request_id] else: @@ -266,10 +385,21 @@ async def handle_message(self, message: aiohttp.WSMessage): else: self.logger.warning(f"Unknown type of message {repr(message)}") - async def empty_handler(self, **kwargs): + async def empty_handler(self, **kwargs) -> None: + """ + A default handler + :param kwargs: + :return: + """ self.logger.debug(f"{repr(kwargs)}") - async def handle_response(self, request, response): + async def handle_response(self, request, response) -> None: + """ + + :param request: + :param response: + :return: + """ method = request["method"] handler = resolve_route(method, self.response_routes) @@ -277,9 +407,13 @@ async def handle_response(self, request, response): return await handler(request=request, response=response) self.logger.warning(f"Unhandled method:{method} response:{repr(response)} to request:{repr(request)}.") - return - async def handle_method_message(self, data): + async def handle_method_message(self, data) -> None: + """ + + :param data: + :return: + """ method = data["method"] handler = resolve_route(method, self.method_routes) @@ -287,15 +421,23 @@ async def handle_method_message(self, data): return await handler(data) self.logger.warning(f"Unhandled message:{repr(data)}.") - return async def handle_heartbeat(self, data): + """ + + :param data: + :return: + """ if data["params"]["type"] == "test_request": await self.send_public({"method": "public/test", "params": {}}) - return + async def handle_auth(self, request, response) -> None: + """ - async def handle_auth(self, request, response): + :param request: + :param response: + :return: + """ self.auth_params = response["result"] grant_type = request["params"]["grant_type"] if grant_type == "": @@ -311,9 +453,3 @@ async def handle_auth(self, request, response): pass else: self.logger.error(f"Unknown grant_type {repr(hide_secret(request))} : {repr(hide_secret(response))}") - - def close(self): - super()._close() - - async def stop(self): - await self.ws.close() From e591c15e70835657aa13fbe49afb6f64ee7b3bd0 Mon Sep 17 00:00:00 2001 From: Oleg Nedbaylo Date: Fri, 5 Jun 2020 12:04:22 +0200 Subject: [PATCH 2/3] demonstrates the use of get_currencies and get_instruments --- examples/deribit_basic_example.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/examples/deribit_basic_example.py b/examples/deribit_basic_example.py index 3a6688f..e0675b2 100644 --- a/examples/deribit_basic_example.py +++ b/examples/deribit_basic_example.py @@ -1,9 +1,30 @@ #!/usr/bin/env python import asyncio +import json from ssc2ce import Deribit conn = Deribit() +pending = {} + + +async def handle_instruments(data: dict): + del pending[data["id"]] + print(json.dumps(data)) + if not pending: + await subscribe() + + +async def handle_currencies(data: dict): + for currency in data["result"]: + symbol = currency["currency"] + id = await conn.get_instruments(symbol, callback=handle_instruments) + pending[id] = symbol + + +async def get_currencies(): + await conn.get_currencies(handle_currencies) + async def subscribe(): await conn.send_public(request={ @@ -23,7 +44,7 @@ async def handle_subscription(data): print(f" Deribit Price Index {index_name.upper()}: {price}") -conn.on_connect_ws = subscribe +conn.on_connect_ws = get_currencies conn.method_routes += [("subscription", handle_subscription)] loop = asyncio.get_event_loop() From 4ca610c3bb5dc3fbcae4e4344abc6b3508544b6b Mon Sep 17 00:00:00 2001 From: Oleg Nedbaylo Date: Fri, 5 Jun 2020 12:04:41 +0200 Subject: [PATCH 3/3] version="0.9.0" --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 39ef700..9f3db89 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name='ssc2ce', - version="0.8.2", + version="0.9.0", author='Oleg Nedbaylo', author_email='olned64@gmail.com', description='A Set of Simple Connectors for access To Cryptocurrency Exchanges',