diff --git a/.gitignore b/.gitignore index 23d4bd7..502819c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +__pycache__/ +test.py + target/ !.mvn/wrapper/maven-wrapper.jar diff --git a/DexilonClientImpl.py b/DexilonClientImpl.py index 0a5bc81..5d2e3c5 100644 --- a/DexilonClientImpl.py +++ b/DexilonClientImpl.py @@ -1,6 +1,7 @@ +import json import time +from typing import List, Any -import requests as requests from pydantic import BaseModel, parse_obj_as from web3.auto import w3 @@ -8,28 +9,23 @@ from eth_keys import keys from DexilonClient import DexilonClient -from ErrorBody import ErrorBody -from OrderErrorInfo import OrderErrorInfo from SessionClient import SessionClient -from exceptions import DexilonAPIException, DexilonRequestException, DexilonAuthException -from typing import List + +from exceptions import DexilonAuthException, DexilonRequestException, DexilonErrorBodyException, OrderErrorInfo from responses import AvailableSymbol, OrderBookInfo, NonceResponse, JWTTokenResponse, OrderEvent, \ - ServiceResponse, ErrorBody, AccountInfo, OrderInfo, AllOpenOrders, \ + ErrorBody, AccountInfo, OrderInfo, AllOpenOrders, \ FullOrderInfo, LeverageUpdateInfo class DexilonClientImpl(DexilonClient): - API_URL = 'https://dex-dev-api.cronrate.com/api/v1' - - JWT_KEY = '' - REFRESH_TOKEN = '' - pk1 = '' + API_URL: str = 'https://dex-dev-api.cronrate.com/api/v1' - headers = {'Content-Type': 'application/json', 'Accept': 'application/json'} + JWT_KEY: str = '' + REFRESH_TOKEN: str = '' - def __init__(self, metamask_address, api_secret): + def __init__(self, metamask_address: str, api_secret: str) -> None: """ Dexilon API Client constructor :param metamask_address: Public Metamask Address @@ -38,11 +34,20 @@ def __init__(self, metamask_address, api_secret): :type api_secret: str. """ - self.METAMASK_ADDRESS = metamask_address.lower() - self.headers['MetamaskAddress'] = self.METAMASK_ADDRESS - self.API_SECRET = api_secret - self.pk1 = keys.PrivateKey(bytes.fromhex(api_secret)) - self.client: SessionClient = SessionClient(self.API_URL, self.headers) + self.METAMASK_ADDRESS: str = metamask_address.lower() + + self.API_SECRET: str = api_secret + self.pk1: keys.PrivateKey = keys.PrivateKey(bytes.fromhex(api_secret)) + self.client: SessionClient = SessionClient(self.API_URL, { + 'MetamaskAddress': self.METAMASK_ADDRESS + }) + + def check_authentication(func): + def method(self: 'DexilonClientImpl', *args, **kwargs): + if not self.JWT_KEY: + self.authenticate() + return func(self, *args, **kwargs) + return method def change_api_url(self, api_url): """ @@ -56,85 +61,106 @@ def change_api_url(self, api_url): self.API_URL = api_url self.client.base_url = api_url + @check_authentication def get_open_orders(self) -> List[OrderInfo]: - self.check_authentication() - all_open_orders_response = self._request('GET', '/orders/open', model=AllOpenOrders) + all_open_orders_response = self._request( + 'GET', '/orders/open', model=AllOpenOrders + ) if isinstance(all_open_orders_response, AllOpenOrders): return all_open_orders_response.content return all_open_orders_response + @check_authentication def get_order_info(self, order_id: str, symbol: str) -> FullOrderInfo: - self.check_authentication() get_order_info_request_params = {'symbol': symbol, 'orderId': order_id} - return self._request('GET','/orders', params=get_order_info_request_params, model=FullOrderInfo) + return self._request('GET', '/orders', params=get_order_info_request_params, model=FullOrderInfo) + @check_authentication def market_order(self, client_order_id: str, symbol: str, side: str, size: float): - self.check_authentication() nonce = self.compose_nonce([client_order_id, symbol, side, size]) signed_nonce = self.sign(nonce) - json_request_body = {'clientorderId': client_order_id, 'symbol': symbol, 'side': side, 'size': size, 'nonce': nonce, 'signedNonce': signed_nonce} - order_response = self._request('POST', '/orders/market', data=json_request_body, model=OrderEvent) + json_request_body = { + 'clientorderId': client_order_id, + 'symbol': symbol, + 'side': side, + 'size': size, + 'nonce': nonce, + 'signedNonce': signed_nonce + } + order_response = self._request( + 'POST', '/orders/market', data=json_request_body, model=OrderEvent + ) return self.parse_order_info_response(order_response, 'MARKET', client_order_id) - def compose_nonce(self, kwargs: []): + def compose_nonce(self, kwargs: List[Any]): current_milliseconds = round(time.time() * 1000) nonce_value = '' for param in kwargs: nonce_value = nonce_value + str(param) + ':' return nonce_value + str(current_milliseconds) - def parse_order_info_response(self, order_info_response, order_type, client_order_id): - if isinstance(order_info_response, ServiceResponse) : - return OrderErrorInfo(client_order_id, '', order_info_response.errorBody.name, ';'.join(order_info_response.errorBody.details)) - error_body = self.get_error_body(order_info_response) - if error_body is not None: - return OrderErrorInfo(client_order_id, '', error_body.name, error_body.details) + def parse_order_info_response(self, order_info_response: OrderEvent, order_type: str, client_order_id: str): + if order_info_response.eventType is not None: + if order_info_response.eventType in ['EXECUTED', 'PARTIALLY_EXECUTED', 'APPLIED']: - order_data = order_info_response.event - order_dict = { - "clientOrderId": client_order_id, - "symbol": self.parse_value_or_return_None(order_data, 'symbol'), - "orderId": self.parse_value_or_return_None(order_data, 'orderId'), - "price": self.parse_value_or_return_None(order_data, 'price'), - "amount": self.parse_value_or_return_None(order_data, 'size'), - "filledAmount": self.parse_value_or_return_None(order_data, 'filled'), - "avgPrice": self.parse_value_or_return_None(order_data, 'avgPrice'), - "type": order_type, - "side": self.parse_value_or_return_None(order_data, 'side'), - "status": order_info_response.eventType, - "createdAt": self.parse_value_or_return_None(order_data, 'placedAt'), - "updatedAt": self.parse_value_or_return_None(order_data, 'placedAt') - } - return FullOrderInfo(**order_dict) + order_data: dict = order_info_response.event + + return FullOrderInfo( + clientOrderId=client_order_id, + symbol=order_data.get('symbol'), + orderId=order_data.get('orderId'), + price=order_data.get('price'), + amount=order_data.get('size'), + filledAmount=order_data.get('filled'), + avgPrice=order_data.get('avgPrice'), + type=order_type, + side=order_data.get('side'), + status=order_info_response.eventType, + createdAt=order_data.get('placedAt'), + updatedAt=order_data.get('updatedAt') + ) + if 'REJECTED' == order_info_response.eventType: order_error_data = order_info_response['event'] - return OrderErrorInfo(client_order_id, '', order_info_response.eventType, order_error_data['cause']) - - def parse_value_or_return_None(self, object_to_parse, param_name): - if param_name in object_to_parse: - return object_to_parse[param_name] - else: - return None + raise OrderErrorInfo( + client_order_id=client_order_id, + state=order_info_response.eventType, + message=order_error_data['cause'] + ) + @check_authentication def limit_order(self, client_order_id: str, symbol: str, side: str, price: float, size: float): - self.check_authentication() - nonce = self.compose_nonce([client_order_id, symbol, side, size, price]) + nonce = self.compose_nonce( + [client_order_id, symbol, side, size, price]) signed_nonce = self.sign(nonce) - json_request_body = {'clientorderId': client_order_id, 'symbol': symbol, 'side': side, 'size': size, - 'price': price, 'nonce': nonce, 'signedNonce': signed_nonce} - limit_order_response = self._request('POST', '/orders/limit', data=json_request_body, model=OrderEvent) + json_request_body = { + 'clientorderId': client_order_id, + 'symbol': symbol, + 'side': side, + 'size': size, + 'price': price, + 'nonce': nonce, + 'signedNonce': signed_nonce + } + limit_order_response = self._request( + 'POST', '/orders/limit', data=json_request_body, model=OrderEvent + ) return self.parse_order_info_response(limit_order_response, 'LIMIT', client_order_id) + @check_authentication def cancel_all_orders(self) -> bool: - self.check_authentication() - cancel_all_orders_response = self._request('DELETE', '/orders/batch', model=List[OrderEvent]) + cancel_all_orders_response = self._request( + 'DELETE', '/orders/batch', model=List[OrderEvent] + ) return isinstance(cancel_all_orders_response, list) + @check_authentication def cancel_order(self, order_id: str, symbol: str): - self.check_authentication() cancel_order_request_body = {'symbol': symbol, 'orderId': order_id} - cancel_order_response = self._request('DELETE', '/orders', params=cancel_order_request_body, model=OrderEvent) + cancel_order_response = self._request( + 'DELETE', '/orders', params=cancel_order_request_body, model=OrderEvent + ) return self.parse_order_info_response(cancel_order_response, '', '') def get_all_symbols(self) -> List[AvailableSymbol]: @@ -144,20 +170,25 @@ def get_orderbook(self, symbol: str) -> OrderBookInfo: orderbook_request = {'symbol': symbol} return self._request('GET', '/orders/book', params=orderbook_request, model=OrderBookInfo) + @check_authentication def get_account_info(self) -> AccountInfo: - self.check_authentication() return self._request('GET', '/accounts', model=AccountInfo) + @check_authentication def set_leverage(self, symbol: str, leverage: int) -> LeverageUpdateInfo: - self.check_authentication() leverage_request = {'symbol': symbol, 'leverage': leverage} return self._request('PUT', '/accounts/leverage', data=leverage_request, model=LeverageUpdateInfo) - def _request(self, method: str, path: str, params: dict = None, data: dict = None, - model: BaseModel = None) -> BaseModel: + def _request(self, + method: str, + path: str, + params: dict = None, + data: dict = None, + model: BaseModel = None + ) -> BaseModel: try: - return self._handle_response_new( + return self._handle_response( response=self.client.request( method=method, path=path, @@ -167,8 +198,10 @@ def _request(self, method: str, path: str, params: dict = None, data: dict = Non model=model ) except DexilonAuthException: + self.authenticate() - return self._handle_response_new( + + return self._handle_response( response=self.client.request( method=method, path=path, @@ -178,46 +211,26 @@ def _request(self, method: str, path: str, params: dict = None, data: dict = Non model=model ) - def _handle_response_new(self, response: dict, model: BaseModel = None) -> BaseModel: + def _handle_response(self, response: dict, model: BaseModel = None) -> BaseModel: data: dict = response['body'] if data is None: - service_response = parse_obj_as(ServiceResponse, response) - return service_response + error_body: dict = response.get('errorBody') + if error_body: + raise DexilonErrorBodyException( + ErrorBody( + code=error_body.get('code'), + name=error_body.get('name'), + details=error_body.get('details', []) + ) + ) + else: + raise DexilonRequestException( + 'body and errorBody is empty in response %s' % json.dumps(response)) if model: return parse_obj_as(model, data) else: return data - def request_get(self, uri, params_request): - r = requests.get(self.API_URL + uri, headers=self.headers, params=params_request) - response = self.handle_response(r) - if r.status_code == 401: - print('The user is not authorized to perform the request. Reauthorizing...') - self.authenticate() - r = requests.get(self.API_URL + uri, headers=self.headers, params=params_request) - return self.handle_response(r) - return response - - def request_post(self, uri, **kwargs): - r = requests.post(self.API_URL + uri, headers=self.headers, json=kwargs) - response = self.handle_response(r) - if r.status_code == 401: - print('The user is not authorized to perform the request. Reauthorizing...') - self.authenticate() - r = requests.post(self.API_URL + uri, headers=self.headers, json=kwargs) - return self.handle_response(r) - return response - - def get_error_body(self, response) -> ErrorBody: - if 'errorBody' in response and response['errorBody'] is not None: - error_body = response['errorBody'] - return ErrorBody(error_body['code'], error_body['name'], ';'.join(error_body['details'])) - return None - - def check_authentication(self): - if len(self.JWT_KEY) == 0: - self.authenticate() - def sign(self, nonce: str) -> str: return w3.eth.account.sign_message( encode_defunct(str.encode(nonce)), private_key=self.pk1 @@ -226,43 +239,37 @@ def sign(self, nonce: str) -> str: def authenticate(self): payload = {'metamaskAddress': self.METAMASK_ADDRESS.lower()} self.client.delete_header("MetamaskAddress") - nonce_response = self._request('POST', '/auth/startAuth', data=payload, model=NonceResponse) + nonce_response = self._request( + 'POST', '/auth/startAuth', data=payload, model=NonceResponse + ) + nonce = nonce_response.nonce + if len(nonce) == 0: - print('ERROR: nonce was not received for Authentication request') - print(nonce_response) + raise DexilonAuthException( + 'nonce was not received for Authentication request' + ) - signature_payload = {'metamaskAddress': self.METAMASK_ADDRESS.lower(), 'signedNonce': self.sign(nonce)} - print(signature_payload) + signature_payload = { + 'metamaskAddress': self.METAMASK_ADDRESS.lower(), + 'signedNonce': self.sign(nonce) + } - auth_info = self._request('POST', '/auth/finishAuth', data=signature_payload, model=JWTTokenResponse) + auth_info = self._request( + 'POST', '/auth/finishAuth', data=signature_payload, model=JWTTokenResponse + ) jwt_token = auth_info.accessToken refresh_token = auth_info.refreshToken if jwt_token is None or len(jwt_token) == 0: - raise DexilonAuthException('Was not able to obtain JWT token for authentication') + raise DexilonAuthException( + 'Was not able to obtain JWT token for authentication' + ) + + self.client.update_headers({ + 'Authorization': 'Bearer ' + jwt_token, + 'MetamaskAddress': self.METAMASK_ADDRESS.lower() + }) - print(jwt_token) - self.client.update_headers({'Authorization': 'Bearer ' + jwt_token, 'MetamaskAddress': self.METAMASK_ADDRESS.lower()}) - self.headers['Authorization'] = 'Bearer ' + jwt_token - self.headers['MetamaskAddress'] = self.METAMASK_ADDRESS.lower() self.JWT_KEY = jwt_token self.REFRESH_TOKEN = refresh_token - - def _handle_response(self, response): - """Internal helper for handling API responses from the Dexilon server. - Raises the appropriate exceptions when necessary; otherwise, returns the - response. - """ - if not str(response.status_code).startswith('2'): - raise DexilonAPIException(response) - try: - return response.json() - except ValueError: - raise DexilonRequestException('Invalid Response: %s' % response.text) - - def handle_response(self, response): - try: - return response.json() - except ValueError: - raise DexilonRequestException('Invalid Response: %s' % response.text) diff --git a/ErrorBody.py b/ErrorBody.py deleted file mode 100644 index 6b8d044..0000000 --- a/ErrorBody.py +++ /dev/null @@ -1,7 +0,0 @@ - -class ErrorBody: - - def __init__(self, code: int, name: str, details: str): - self.code = code - self.name = name - self.details = details \ No newline at end of file diff --git a/OrderErrorInfo.py b/OrderErrorInfo.py deleted file mode 100644 index 8634c45..0000000 --- a/OrderErrorInfo.py +++ /dev/null @@ -1,8 +0,0 @@ - -class OrderErrorInfo: - - def __init__(self, client_order_id: str, order_id: str, state: str, message: str): - self.client_order_id = client_order_id - self.order_id = order_id - self.state = state - self.message = message \ No newline at end of file diff --git a/SessionClient.py b/SessionClient.py index e466c50..793e171 100644 --- a/SessionClient.py +++ b/SessionClient.py @@ -1,3 +1,5 @@ +from typing import Set + import requests from exceptions import DexilonAPIException, DexilonRequestException, DexilonAuthException @@ -5,13 +7,14 @@ class SessionClient: - def __init__(self, base_url: str, headers: dict = {}) -> None: + STATUS_CODES_TO_PROCESS: Set[int] = {200, 400, 401} - self.STATUS_CODES_TO_PROCESS = {200, 400, 401} + def __init__(self, base_url: str, headers: dict = {}) -> None: self.base_url: str = base_url self.session: requests.Session = requests.Session() - self.session.headers.update(headers) + + self.update_headers(headers) def update_headers(self, headers: dict = {}) -> None: self.session.headers.update(headers) @@ -20,6 +23,7 @@ def delete_header(self, header_name: str) -> None: self.session.headers.pop(header_name) def request(self, method: str, path: str, params: dict = None, data: dict = None) -> dict: + request = requests.Request( method=method.upper(), url=self.base_url + path, diff --git a/exceptions.py b/exceptions.py index 501aa63..b677c87 100644 --- a/exceptions.py +++ b/exceptions.py @@ -1,3 +1,6 @@ +from responses import ErrorBody + + class DexilonAPIException(Exception): def __init__(self, response): @@ -5,7 +8,8 @@ def __init__(self, response): try: json_res = response.json() except ValueError: - self.message = 'Invalid JSON error message from Dexilon: {}'.format(response.text) + self.message = 'Invalid JSON error message from Dexilon: {}'.format( + response.text) else: self.code = json_res['errors']['code'] self.message = json_res['errors']['message'] @@ -25,9 +29,28 @@ def __str__(self): return 'DexilonRequestException: %s' % self.message -class DexilonAuthException(Exception) : +class DexilonAuthException(Exception): def __init__(self, message): self.message = message def __str__(self): return 'DexilonAuthException: %s' % self.message + + +class DexilonErrorBodyException(Exception): + def __init__(self, error_body: ErrorBody): + self.error_body = error_body + + def __str__(self): + return 'ErrorBodyException: %s' % self.error_body + + +class OrderErrorInfo(Exception): + + def __init__(self, client_order_id: str, state: str, message: str): + self.client_order_id = client_order_id + self.state = state + self.message = message + + def __str__(self): + return 'OrderErrorInfo: client_order_id=%s state=%s message=%s' % (self.client_order_id, self.state, self.message) diff --git a/responses.py b/responses.py index 496d593..ea4e537 100644 --- a/responses.py +++ b/responses.py @@ -3,19 +3,17 @@ from pydantic import BaseModel, Field from pydantic.main import Optional + class ErrorBody(BaseModel): code: int name: str details: List[str] + class DebugInfo(BaseModel): correlationId: Optional[str] stackTrace: Optional[str] -class ServiceResponse(BaseModel): - errorBody: Optional[ErrorBody] - debugInfo: Optional[DebugInfo] - class AvailableSymbol(BaseModel): symbol: str @@ -107,7 +105,7 @@ class FullOrderInfo(BaseModel): side: str status: str createdAt: datetime - updatedAt: datetime + updatedAt: Optional[datetime] class LeverageEvent(BaseModel): diff --git a/tests/authentication_test.py b/tests/authentication_test.py index a75ba2e..6559ac5 100644 --- a/tests/authentication_test.py +++ b/tests/authentication_test.py @@ -1,5 +1,5 @@ from DexilonClientImpl import DexilonClientImpl -from OrderErrorInfo import OrderErrorInfo +from exceptions import OrderErrorInfo # FAKE from responses import FullOrderInfo diff --git a/tests/trading_integration_test.py b/tests/trading_integration_test.py index 8502afb..6bdeb58 100644 --- a/tests/trading_integration_test.py +++ b/tests/trading_integration_test.py @@ -1,5 +1,4 @@ from DexilonClientImpl import DexilonClientImpl -from OrderErrorInfo import OrderErrorInfo from responses import FullOrderInfo, LeverageUpdateInfo @@ -11,28 +10,41 @@ class TestTradingIntegration: # TEST_PRIVATE_KEY = '7bf5ae9f4d080107e105212f40cdc3d897d1101e69fd8f0a36e5bb648055ff52' def setup(self): - self.test_instance = DexilonClientImpl(self.TEST_METAMASK_ADDRESS, self.TEST_PRIVATE_KEY) - self.test_instance.change_api_url('https://dex-dev2-api.cronrate.com/api/v1') + self.test_instance = DexilonClientImpl( + self.TEST_METAMASK_ADDRESS, self.TEST_PRIVATE_KEY) + self.test_instance.change_api_url( + 'https://dex-dev2-api.cronrate.com/api/v1') def test_create_market_order(self): - full_order_info = self.test_instance.market_order('TEST_MARKET_ORDER_1', 'eth_usdc', 'SELL', 0.20) - assert isinstance(full_order_info, FullOrderInfo) and full_order_info.orderId is not None + full_order_info = self.test_instance.market_order( + 'TEST_MARKET_ORDER_1', 'eth_usdc', 'SELL', 0.20) + assert isinstance( + full_order_info, FullOrderInfo) and full_order_info.orderId is not None def test_create_market_order_with_rejected_state(self): - order_submit_result = self.test_instance.market_order('TEST_MARKET_ORDER_1', 'eth_usdc', 'BUY', 1000000000.00) + order_submit_result = self.test_instance.market_order( + 'TEST_MARKET_ORDER_1', 'eth_usdc', 'BUY', 1000000000.00) assert isinstance(order_submit_result, OrderErrorInfo) assert 'NEW_ORDER_REJECTED' in order_submit_result.state def test_create_limit_order(self): - full_order_info = self.test_instance.limit_order('TEST_LIMIT_ORDER_2', 'eth_usdc', 'BUY', 1650.0, 0.2) - assert isinstance(full_order_info, FullOrderInfo) and full_order_info.orderId is not None + full_order_info = self.test_instance.limit_order( + 'TEST_LIMIT_ORDER_2', 'eth_usdc', 'BUY', 1000.0, 0.2) + if type(full_order_info) is not FullOrderInfo: + print('Error:', full_order_info.message) + print('Order:', full_order_info) + assert isinstance( + full_order_info, FullOrderInfo) and full_order_info.orderId is not None + return full_order_info def test_create_limit_order_with_rejected_state(self): - full_order_info = self.test_instance.limit_order('TEST_LIMIT_ORDER_2', 'eth_usdc', 'BUY', 3200.0, 100.0) + full_order_info = self.test_instance.limit_order( + 'TEST_LIMIT_ORDER_2', 'eth_usdc', 'BUY', 3200.0, 100.0) assert isinstance(full_order_info, OrderErrorInfo) def test_should_cancel_all_orders(self): result = self.test_instance.cancel_all_orders() + print(result) assert result def test_should_cancel_order(self): @@ -40,22 +52,26 @@ def test_should_cancel_order(self): assert isinstance(result, OrderErrorInfo) def test_should_create_and_cancel_order_sucessfully(self): - full_order_info = self.test_instance.limit_order('TEST_LIMIT_ORDER_2', 'eth_usdc', 'BUY', 1200.0, 0.2) + full_order_info = self.test_instance.limit_order( + 'TEST_LIMIT_ORDER_2', 'eth_usdc', 'BUY', 1200.0, 0.2) assert isinstance(full_order_info, FullOrderInfo) - canceled_order_info = self.test_instance.cancel_order(full_order_info.orderId, full_order_info.symbol) + canceled_order_info = self.test_instance.cancel_order( + full_order_info.orderId, full_order_info.symbol) assert isinstance(canceled_order_info, FullOrderInfo) def test_should_get_account_info(self): account_info_result = self.test_instance.get_account_info() assert account_info_result is not None - def test_should_get_all_open_orders(self): # - full_order_info = self.test_instance.limit_order('TEST_LIMIT_ORDER_2', 'eth_usdc', 'BUY', 1200.0, 0.2) + def test_should_get_all_open_orders(self): + full_order_info = self.test_instance.limit_order( + 'TEST_LIMIT_ORDER_2', 'eth_usdc', 'BUY', 1200.0, 0.2) open_orders = self.test_instance.get_open_orders() assert len(open_orders) > 0 - self.test_instance.cancel_order(full_order_info.orderId, full_order_info.symbol) + self.test_instance.cancel_order( + full_order_info.orderId, full_order_info.symbol) # def test_should_get_order_info(self): # # full_order_info = self.test_instance.limit_order('TEST_LIMIT_ORDER_2', 'eth_usdc', 'BUY', 1200.0, 0.2) @@ -68,19 +84,22 @@ def test_should_get_all_open_orders(self): # # assert isinstance(cancel_result, FullOrderInfo) def test_should_error_on_cancel_wrong_order(self): - cancel_result = self.test_instance.cancel_order('RANDOMID1', 'eth_usdc') + cancel_result = self.test_instance.cancel_order( + 'RANDOMID1', 'eth_usdc') assert isinstance(cancel_result, OrderErrorInfo) def test_should_parse_response_for_illegal_char_in_cancel_request(self): - cancel_result = self.test_instance.cancel_order('RANDOM-ID-1', 'eth_usdc') + cancel_result = self.test_instance.cancel_order( + 'RANDOM-ID-1', 'eth_usdc') assert isinstance(cancel_result, OrderErrorInfo) def test_should_process_error_for_limit_order_submit(self): - order_submit_result = self.test_instance.limit_order('TEST_MARKET_ORDER_1', 'eth_usdc', 'BUY', 1600.00, 0.10) + order_submit_result = self.test_instance.limit_order( + 'TEST_MARKET_ORDER_1', 'eth_usdc', 'BUY', 1600.00, 0.10) assert isinstance(order_submit_result, OrderErrorInfo) if isinstance(order_submit_result, OrderErrorInfo): assert 'REJECTED' in order_submit_result.state def test_should_set_leverage(self): leverage_update = self.test_instance.set_leverage('eth_usdc', 1) - assert isinstance(leverage_update, LeverageUpdateInfo) \ No newline at end of file + assert isinstance(leverage_update, LeverageUpdateInfo)