async def _api_request(self, method: str, path_url: str, params: Optional[Dict[str, Any]] = None, auth_type: str = None) -> Dict[str, Any]: """ Sends an aiohttp request and waits for a response. :param method: The HTTP method, e.g. get or post :param path_url: The path url or the API end point :param params: Request parameters :param auth_type: Type of Authorization header to send in request, from {"SIGNED", "KEYED", None} :returns A response in json format. """ params = params or {} async with self._throttler.execute_task(path_url): url = f"{CONSTANTS.REST_URL}/{path_url}" headers = self._bitmart_auth.get_headers( bitmart_utils.get_ms_timestamp(), params, auth_type) if method == "get": request = RESTRequest(method=RESTMethod.GET, url=url, headers=headers, params=params) rest_assistant = await self._get_rest_assistant() response = await rest_assistant.call(request=request) elif method == "post": post_json = json.dumps(params) request = RESTRequest(method=RESTMethod.POST, url=url, headers=headers, data=post_json) rest_assistant = await self._get_rest_assistant() response = await rest_assistant.call(request=request) else: raise NotImplementedError try: parsed_response = json.loads(await response.text()) except Exception as e: raise IOError( f"Error parsing data from {url}. Error: {str(e)}") if response.status != 200: raise IOError( f"Error calling {url}. HTTP status is {response.status}. " f"Message: {parsed_response['message']}") if int(parsed_response["code"]) != 1000: raise IOError( f"{url} API call failed, error message: {parsed_response['message']}" ) return parsed_response
async def get_snapshot( trading_pair: str, limit: int = 1000, domain: str = CONSTANTS.DOMAIN, throttler: Optional[AsyncThrottler] = None, api_factory: WebAssistantsFactory = None ) -> Dict[str, Any]: ob_source_cls = BinancePerpetualAPIOrderBookDataSource try: api_factory = api_factory or utils.build_api_factory() rest_assistant = await api_factory.get_rest_assistant() params = {"symbol": await ob_source_cls.convert_to_exchange_trading_pair( hb_trading_pair=trading_pair, domain=domain, throttler=throttler)} if limit != 0: params.update({"limit": str(limit)}) url = utils.rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain) throttler = throttler or ob_source_cls._get_throttler_instance() async with throttler.execute_task(limit_id=CONSTANTS.SNAPSHOT_REST_URL): request = RESTRequest( method=RESTMethod.GET, url=url, params=params, ) response = await rest_assistant.call(request=request) if response.status != 200: raise IOError(f"Error fetching Binance market snapshot for {trading_pair}.") data: Dict[str, Any] = await response.json() return data except asyncio.CancelledError: raise except Exception: raise
async def _init_trading_pair_symbols( cls, domain: str = "com", api_factory: Optional[WebAssistantsFactory] = None, throttler: Optional[AsyncThrottler] = None): """ Initialize mapping of trade symbols in exchange notation to trade symbols in client notation """ mapping = bidict() local_api_factory = api_factory or build_api_factory() rest_assistant = await local_api_factory.get_rest_assistant() local_throttler = throttler or cls._get_throttler_instance() url = binance_utils.public_rest_url( path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL, domain=domain) request = RESTRequest(method=RESTMethod.GET, url=url) try: async with local_throttler.execute_task( limit_id=CONSTANTS.EXCHANGE_INFO_PATH_URL): response: RESTResponse = await rest_assistant.call( request=request) if response.status == 200: data = await response.json() for symbol_data in filter( binance_utils.is_exchange_information_valid, data["symbols"]): mapping[symbol_data[ "symbol"]] = f"{symbol_data['baseAsset']}-{symbol_data['quoteAsset']}" except Exception as ex: cls.logger().error( f"There was an error requesting exchange info ({str(ex)})") cls._trading_pair_symbol_map[domain] = mapping
async def get_order_book_data(trading_pair: str) -> Dict[str, any]: """ Get whole orderbook """ throttler = BitmartAPIOrderBookDataSource._get_throttler_instance() async with throttler.execute_task(CONSTANTS.GET_ORDER_BOOK_PATH_URL): request = RESTRequest( method=RESTMethod.GET, url=f"{CONSTANTS.REST_URL}/{CONSTANTS.GET_ORDER_BOOK_PATH_URL}?size=200&symbol=" f"{convert_to_exchange_trading_pair(trading_pair)}", ) rest_assistant = await build_api_factory().get_rest_assistant() response = await rest_assistant.call(request=request, timeout=10) if response.status != 200: raise IOError( f"Error fetching OrderBook for {trading_pair} at {CONSTANTS.EXCHANGE_NAME}. " f"HTTP status is {response.status}." ) orderbook_data: Dict[str, Any] = await response.json() orderbook_data = orderbook_data["data"] return orderbook_data
async def get_all_mid_prices(domain="com") -> Dict[str, Decimal]: """ Returns the mid price of all trading pairs, obtaining the information from the exchange. This functionality is required by the market price strategy. :param domain: Domain to use for the connection with the exchange (either "com" or "us"). Default value is "com" :return: Dictionary with the trading pair as key, and the mid price as value """ local_api_factory = build_api_factory() rest_assistant = await local_api_factory.get_rest_assistant() throttler = BinanceAPIOrderBookDataSource._get_throttler_instance() url = binance_utils.public_rest_url( path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL, domain=domain) request = RESTRequest(method=RESTMethod.GET, url=url) async with throttler.execute_task( limit_id=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL): resp: RESTResponse = await rest_assistant.call(request=request) resp_json = await resp.json() ret_val = {} for record in resp_json: try: pair = await BinanceAPIOrderBookDataSource.trading_pair_associated_to_exchange_symbol( symbol=record["symbol"], domain=domain) ret_val[pair] = ((Decimal(record.get("bidPrice", "0")) + Decimal(record.get("askPrice", "0"))) / Decimal("2")) except KeyError: # Ignore results for pairs that are not tracked continue return ret_val
def test_rest_authenticate(self): now = 1234567890.000 mock_time_provider = MagicMock() mock_time_provider.time.return_value = now params = { "symbol": "LTCBTC", "side": "BUY", "type": "LIMIT", "timeInForce": "GTC", "quantity": 1, "price": "0.1", } full_params = copy(params) auth = BinanceAuth(api_key=self._api_key, secret_key=self._secret, time_provider=mock_time_provider) request = RESTRequest(method=RESTMethod.GET, params=params, is_auth_required=True) configured_request = self.async_run_with_timeout(auth.rest_authenticate(request)) full_params.update({"timestamp": 1234567890000}) encoded_params = "&".join([f"{key}={value}" for key, value in full_params.items()]) expected_signature = hmac.new( self._secret.encode("utf-8"), encoded_params.encode("utf-8"), hashlib.sha256).hexdigest() self.assertEqual(now * 1e3, configured_request.params["timestamp"]) self.assertEqual(expected_signature, configured_request.params["signature"]) self.assertEqual({"X-MBX-APIKEY": self._api_key}, configured_request.headers)
async def _init_trading_pair_symbols( cls, api_factory: Optional[WebAssistantsFactory] = None, throttler: Optional[AsyncThrottler] = None): """ Initialize mapping of trade symbols in exchange notation to trade symbols in client notation """ mapping = bidict() throttler = throttler or cls._get_throttler_instance() api_factory = api_factory or build_api_factory(throttler=throttler) rest_assistant = await api_factory.get_rest_assistant() url = f"{CONSTANTS.REST_URL}/{CONSTANTS.PRODUCTS_PATH_URL}" request = RESTRequest(method=RESTMethod.GET, url=url) try: async with throttler.execute_task( limit_id=CONSTANTS.PRODUCTS_PATH_URL): response: RESTResponse = await rest_assistant.call( request=request) if response.status == 200: data: Dict[str, Dict[str, Any]] = await response.json() for symbol_data in data["data"]: mapping[symbol_data[ "symbol"]] = f"{symbol_data['baseAsset']}-{symbol_data['quoteAsset']}" except Exception as ex: cls.logger().error( f"There was an error requesting exchange info ({str(ex)})") cls._trading_pair_symbol_map = mapping
async def fetch_trading_pairs(cls, throttler: Optional[AsyncThrottler] = None ) -> List[str]: throttler = throttler or cls._get_throttler_instance() try: async with throttler.execute_task(CONSTANTS.ASSET_PAIRS_PATH_URL): url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ASSET_PAIRS_PATH_URL}" request = RESTRequest(method=RESTMethod.GET, url=url) rest_assistant = await build_api_factory( throttler=throttler).get_rest_assistant() response = await rest_assistant.call(request, timeout=5) if response.status == 200: data: Dict[str, Any] = await response.json() raw_pairs = data.get("result", []) converted_pairs: List[str] = [] for pair, details in raw_pairs.items(): if "." not in pair: try: wsname = details[ "wsname"] # pair in format BASE/QUOTE converted_pairs.append( convert_from_exchange_trading_pair(wsname)) except IOError: pass return [item for item in converted_pairs] except Exception: pass # Do nothing if the request fails -- there will be no autocomplete for kraken trading pairs return []
async def get_snapshot(self, trading_pair: str, limit: int = 1000) -> Dict[str, Any]: """ Retrieves a copy of the full order book from the exchange, for a particular trading pair. :param trading_pair: the trading pair for which the order book will be retrieved :param limit: the depth of the order book to retrieve :return: the response from the exchange (JSON dictionary) """ rest_assistant = await self._get_rest_assistant() params = { "symbol": await self.exchange_symbol_associated_to_pair( trading_pair=trading_pair, domain=self._domain, api_factory=self._api_factory, throttler=self._throttler) } if limit != 0: params["limit"] = str(limit) url = binance_utils.public_rest_url( path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self._domain) request = RESTRequest(method=RESTMethod.GET, url=url, params=params) async with self._throttler.execute_task( limit_id=CONSTANTS.SNAPSHOT_PATH_URL): response: RESTResponse = await rest_assistant.call(request=request) if response.status != 200: raise IOError( f"Error fetching market snapshot for {trading_pair}. " f"Response: {response}.") data = await response.json() return data
def test_add_auth_headers_to_post_request(self): body = {"param_z": "value_param_z", "param_a": "value_param_a"} request = RESTRequest(method=RESTMethod.POST, url="https://test.url/api/endpoint", data=json.dumps(body), is_auth_required=True, throttler_limit_id="/api/endpoint") self.async_run_with_timeout(self.auth.rest_authenticate(request)) self.assertEqual(self.api_key, request.headers["KC-API-KEY"]) self.assertEqual("1000000", request.headers["KC-API-TIMESTAMP"]) self.assertEqual("2", request.headers["KC-API-KEY-VERSION"]) expected_signature = self._sign( "1000000" + "POST" + request.throttler_limit_id + json.dumps(body), key=self.secret_key) self.assertEqual(expected_signature, request.headers["KC-API-SIGN"]) expected_passphrase = self._sign(self.passphrase, key=self.secret_key) self.assertEqual(expected_passphrase, request.headers["KC-API-PASSPHRASE"]) self.assertEqual(CONSTANTS.HB_PARTNER_ID, request.headers["KC-API-PARTNER"]) expected_partner_signature = self._sign( "1000000" + CONSTANTS.HB_PARTNER_ID + self.api_key, key=CONSTANTS.HB_PARTNER_KEY) self.assertEqual(expected_partner_signature, request.headers["KC-API-PARTNER-SIGN"])
def test_add_auth_headers_to_get_request_with_params(self): request = RESTRequest(method=RESTMethod.GET, url="https://test.url/api/endpoint", params={ "param_z": "value_param_z", "param_a": "value_param_a" }, is_auth_required=True, throttler_limit_id="/api/endpoint") self.async_run_with_timeout(self.auth.rest_authenticate(request)) self.assertEqual(self.api_key, request.headers["KC-API-KEY"]) self.assertEqual("1000000", request.headers["KC-API-TIMESTAMP"]) self.assertEqual("2", request.headers["KC-API-KEY-VERSION"]) full_endpoint = f"{request.throttler_limit_id}?param_a=value_param_a¶m_z=value_param_z" expected_signature = self._sign("1000000" + "GET" + full_endpoint, key=self.secret_key) self.assertEqual(expected_signature, request.headers["KC-API-SIGN"]) expected_passphrase = self._sign(self.passphrase, key=self.secret_key) self.assertEqual(expected_passphrase, request.headers["KC-API-PASSPHRASE"]) self.assertEqual(CONSTANTS.HB_PARTNER_ID, request.headers["KC-API-PARTNER"]) expected_partner_signature = self._sign( "1000000" + CONSTANTS.HB_PARTNER_ID + self.api_key, key=CONSTANTS.HB_PARTNER_KEY) self.assertEqual(expected_partner_signature, request.headers["KC-API-PARTNER-SIGN"])
async def get_auth_token(self) -> str: api_auth: Dict[str, Any] = self._kraken_auth.generate_auth_dict( uri=CONSTANTS.GET_TOKEN_PATH_URL) url = f"{CONSTANTS.BASE_URL}{CONSTANTS.GET_TOKEN_PATH_URL}" request = RESTRequest(method=RESTMethod.POST, url=url, headers=api_auth["headers"], data=api_auth["postDict"]) rest_assistant = await self._get_rest_assistant() async with self._throttler.execute_task(CONSTANTS.GET_TOKEN_PATH_URL): response = await rest_assistant.call(request=request, timeout=100) if response.status != 200: raise IOError( f"Error fetching Kraken user stream listen key. HTTP status is {response.status}." ) try: response_json: Dict[str, Any] = await response.json() except Exception: raise IOError(f"Error parsing data from {url}.") err = response_json["error"] if "EAPI:Invalid nonce" in err: self.logger().error( f"Invalid nonce error from {url}. " + "Please ensure your Kraken API key nonce window is at least 10, " + "and if needed reset your API key.") raise IOError({"error": response_json}) return response_json["result"]["token"]
def test_rest_assistant_call_with_pre_and_post_processing(self, mocked_api): url = "https://www.test.com/url" resp = {"one": 1} pre_processor_ran = False post_processor_ran = False mocked_api.get(url, body=json.dumps(resp).encode()) class PreProcessor(RESTPreProcessorBase): async def pre_process(self, request: RESTRequest) -> RESTRequest: nonlocal pre_processor_ran pre_processor_ran = True return request class PostProcessor(RESTPostProcessorBase): async def post_process(self, response: RESTResponse) -> RESTResponse: nonlocal post_processor_ran post_processor_ran = True return response pre_processors = [PreProcessor()] post_processors = [PostProcessor()] connection = RESTConnection(aiohttp.ClientSession()) assistant = RESTAssistant( connection=connection, throttler=AsyncThrottler(rate_limits=[]), rest_pre_processors=pre_processors, rest_post_processors=post_processors) req = RESTRequest(method=RESTMethod.GET, url=url) ret = self.async_run_with_timeout(assistant.call(req)) ret_json = self.async_run_with_timeout(ret.json()) self.assertEqual(resp, ret_json) self.assertTrue(pre_processor_ran) self.assertTrue(post_processor_ran)
async def listen_for_user_stream(self, output: asyncio.Queue): """ *required Subscribe to user stream via web socket, and keep the connection open for incoming messages :param output: an async queue where the incoming messages are stored """ ws = None while True: try: rest_assistant = await self._api_factory.get_rest_assistant() url = f"{CONSTANTS.REST_URL}/{CONSTANTS.INFO_PATH_URL}" request = RESTRequest(method=RESTMethod.GET, url=url, endpoint_url=CONSTANTS.INFO_PATH_URL, is_auth_required=True) async with self._throttler.execute_task( CONSTANTS.INFO_PATH_URL): response: RESTResponse = await rest_assistant.call( request=request) info = await response.json() accountGroup = info.get("data").get("accountGroup") headers = self._ascend_ex_auth.get_auth_headers( CONSTANTS.STREAM_PATH_URL) payload = { "op": CONSTANTS.SUB_ENDPOINT_NAME, "ch": "order:cash" } ws: WSAssistant = await self._get_ws_assistant() url = f"{get_ws_url_private(accountGroup)}/{CONSTANTS.STREAM_PATH_URL}" await ws.connect(ws_url=url, ws_headers=headers, ping_timeout=self.HEARTBEAT_PING_INTERVAL) subscribe_request: WSJSONRequest = WSJSONRequest(payload) async with self._throttler.execute_task( CONSTANTS.SUB_ENDPOINT_NAME): await ws.send(subscribe_request) async for raw_msg in ws.iter_messages(): msg = raw_msg.data if msg is None: continue self._last_recv_time = time.time() output.put_nowait(msg) except asyncio.CancelledError: raise except Exception: self.logger().error( "Unexpected error with AscendEx WebSocket connection. " "Retrying after 30 seconds...", exc_info=True) await self._sleep(30.0) finally: ws and await ws.disconnect()
async def api_request(path: str, api_factory: Optional[WebAssistantsFactory] = None, throttler: Optional[AsyncThrottler] = None, time_synchronizer: Optional[TimeSynchronizer] = None, domain: str = CONSTANTS.DEFAULT_DOMAIN, params: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None, method: RESTMethod = RESTMethod.GET, is_auth_required: bool = False, return_err: bool = False, limit_id: Optional[str] = None, timeout: Optional[float] = None, headers: Dict[str, Any] = {}): throttler = throttler or create_throttler() time_synchronizer = time_synchronizer or TimeSynchronizer() # If api_factory is not provided a default one is created # The default instance has no authentication capabilities and all authenticated requests will fail api_factory = api_factory or build_api_factory( throttler=throttler, time_synchronizer=time_synchronizer, domain=domain, ) rest_assistant = await api_factory.get_rest_assistant() local_headers = {"Content-Type": "application/x-www-form-urlencoded"} local_headers.update(headers) url = rest_url(path, domain=domain) request = RESTRequest(method=method, url=url, params=params, data=data, headers=local_headers, is_auth_required=is_auth_required, throttler_limit_id=limit_id if limit_id else path) async with throttler.execute_task(limit_id=limit_id if limit_id else path): response = await rest_assistant.call(request=request, timeout=timeout) if response.status != 200: if return_err: error_response = await response.json() return error_response else: error_response = await response.text() if error_response is not None and "ret_code" in error_response and "ret_msg" in error_response: raise IOError( f"The request to Bybit failed. Error: {error_response}. Request: {request}" ) else: raise IOError( f"Error executing request {method.name} {path}. " f"HTTP status is {response.status}. " f"Error: {error_response}") return await response.json()
def test_binance_perpetual_rest_pre_processor_post_request(self): request: RESTRequest = RESTRequest( method=RESTMethod.POST, url="/TEST_URL", ) result_request: RESTRequest = self.async_run_with_timeout(self.pre_processor.pre_process(request)) self.assertIn("Content-Type", result_request.headers) self.assertEqual(result_request.headers["Content-Type"], "application/json")
async def rest_auth(self, path_url: str) -> Dict[Any, Any]: """REST private GET request""" url = web_utils.private_rest_url(path_url=path_url, domain=self.domain) headers = {"Content-Type": "application/x-www-form-urlencoded"} request = RESTRequest(method=RESTMethod.GET, url=url, headers=headers, is_auth_required=True) client = await self.api_factory.get_rest_assistant() response: RESTResponse = await client.call(request) return await response.json()
async def _update_trading_rules(self): request = RESTRequest( method=RESTMethod.GET, url=f"{CONSTANTS.REST_URL}/{CONSTANTS.GET_TRADING_RULES_PATH_URL}", ) rest_assistant = await self._get_rest_assistant() response = await rest_assistant.call(request=request) symbols_details: Dict[str, Any] = await response.json() self._trading_rules.clear() self._trading_rules = self._format_trading_rules(symbols_details)
def test_bitmex_perpetual_rest_pre_processor_non_post_request(self): request: RESTRequest = RESTRequest( method=RESTMethod.GET, url="/TEST_URL", ) result_request: RESTRequest = self.async_run_with_timeout( self.pre_processor.pre_process(request)) self.assertIn("Content-Type", result_request.headers) self.assertEqual(result_request.headers["Content-Type"], "application/x-www-form-urlencoded")
def test_rest_authenticate_no_parameters_provided(self): request: RESTRequest = RESTRequest(method=RESTMethod.GET, url="/TEST_PATH_URL", is_auth_required=True) signed_request: RESTRequest = self.async_run_with_timeout( self.auth.rest_authenticate(request)) self.assertIn("X-MBX-APIKEY", signed_request.headers) self.assertEqual(signed_request.headers["X-MBX-APIKEY"], self.api_key) self.assertIsNone(signed_request.params) self.assertIsNone(signed_request.data)
async def _get_last_traded_price(cls, trading_pair: str, throttler: AsyncThrottler) -> float: url = (f"{CONSTANTS.BASE_URL}{CONSTANTS.TICKER_PATH_URL}" f"?pair={convert_to_exchange_trading_pair(trading_pair)}") request = RESTRequest(method=RESTMethod.GET, url=url) rest_assistant = await build_api_factory().get_rest_assistant() async with throttler.execute_task(CONSTANTS.TICKER_PATH_URL): resp = await rest_assistant.call(request) resp_json = await resp.json() record = list(resp_json["result"].values())[0] return float(record["c"][0])
def test_rest_assistant_authenticates(self, mocked_call): url = "https://www.test.com/url" resp = {"one": 1} call_request: Optional[RESTRequest] = None auth_header = {"authenticated": True} async def register_request_and_return(request: RESTRequest): nonlocal call_request call_request = request return resp mocked_call.side_effect = register_request_and_return class AuthDummy(AuthBase): async def rest_authenticate(self, request: RESTRequest) -> RESTRequest: request.headers = auth_header return request async def ws_authenticate(self, request: WSRequest) -> WSRequest: pass connection = RESTConnection(aiohttp.ClientSession()) assistant = RESTAssistant(connection, auth=AuthDummy()) req = RESTRequest(method=RESTMethod.GET, url=url) auth_req = RESTRequest(method=RESTMethod.GET, url=url, is_auth_required=True) self.async_run_with_timeout(assistant.call(req)) self.assertIsNotNone(call_request) self.assertIsNone(call_request.headers) self.async_run_with_timeout(assistant.call(auth_req)) self.assertIsNotNone(call_request) self.assertIsNotNone(call_request.headers) self.assertEqual(call_request.headers, auth_header)
def test_add_auth_params_to_get_request_without_params(self): request = RESTRequest( method=RESTMethod.GET, url="https://test.url/api/endpoint", is_auth_required=True, throttler_limit_id="/api/endpoint" ) params_expected = self._params_expected(request.params) self.async_run_with_timeout(self.auth.rest_authenticate(request)) self.assertEqual(params_expected['api_key'], request.params["api_key"]) self.assertEqual(params_expected['timestamp'], request.params["timestamp"]) self.assertEqual(params_expected['sign'], request.params["sign"])
async def api_request(path: str, api_factory: Optional[WebAssistantsFactory] = None, throttler: Optional[AsyncThrottler] = None, time_synchronizer: Optional[TimeSynchronizer] = None, domain: str = CONSTANTS.DEFAULT_DOMAIN, params: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None, method: RESTMethod = RESTMethod.GET, is_auth_required: bool = False, return_err: bool = False, limit_id: Optional[str] = None, timeout: Optional[float] = None, headers=None) -> Union[str, Dict[str, Any]]: if headers is None: headers = {} throttler = throttler or create_throttler() time_synchronizer = time_synchronizer or TimeSynchronizer() # If api_factory is not provided a default one is created # The default instance has no authentication capabilities and all authenticated requests will fail api_factory = api_factory or build_api_factory( throttler=throttler, time_synchronizer=time_synchronizer, domain=domain, ) rest_assistant = await api_factory.get_rest_assistant() local_headers = {"Content-Type": "application/json"} local_headers.update(headers) url = private_rest_url( path, domain=domain) if is_auth_required else public_rest_url( path, domain=domain) # top_level_function_name = cast(FrameType, cast(FrameType, inspect.currentframe()).f_back).f_code.co_name # print(f"top_level_function_name={top_level_function_name} limit_id={limit_id} url={url}") request = RESTRequest(method=method, url=url, params=params, data=data, headers=local_headers, is_auth_required=is_auth_required, throttler_limit_id=limit_id if limit_id else path) async with throttler.execute_task(limit_id=limit_id if limit_id else path): response = await rest_assistant.call(request=request, timeout=timeout) if response.status != 200 and not return_err: raise IOError(f"Error for Response: {response}.") return await response.json()
async def api_request(path: str, api_factory: Optional[WebAssistantsFactory] = None, throttler: Optional[AsyncThrottler] = None, time_synchronizer: Optional[TimeSynchronizer] = None, domain: str = CONSTANTS.DOMAIN, params: Optional[Dict[str, Any]] = None, data: Optional[Dict[str, Any]] = None, method: RESTMethod = RESTMethod.GET, is_auth_required: bool = False, return_err: bool = False, api_version: str = CONSTANTS.API_VERSION, limit_id: Optional[str] = None, timeout: Optional[float] = None): throttler = throttler or create_throttler() time_synchronizer = time_synchronizer or TimeSynchronizer() # If api_factory is not provided a default one is created # The default instance has no authentication capabilities and all authenticated requests will fail api_factory = api_factory or build_api_factory( throttler=throttler, time_synchronizer=time_synchronizer, domain=domain, ) rest_assistant = await api_factory.get_rest_assistant() async with throttler.execute_task(limit_id=limit_id if limit_id else path): url = rest_url(path, domain, api_version) request = RESTRequest( method=method, url=url, params=params, data=data, is_auth_required=is_auth_required, throttler_limit_id=limit_id if limit_id else path ) response = await rest_assistant.call(request=request, timeout=timeout) if response.status != 200: if return_err: error_response = await response.json() return error_response else: error_response = await response.text() raise IOError(f"Error executing request {method.name} {path}. " f"HTTP status is {response.status}. " f"Error: {error_response}") return await response.json()
async def get_last_traded_prices( cls, trading_pairs: List[str], api_factory: Optional[WebAssistantsFactory] = None, throttler: Optional[AsyncThrottler] = None) -> Dict[str, float]: """ Return a dictionary the trading_pair as key and the current price as value for each trading pair passed as parameter :param trading_pairs: list of trading pairs to get the prices for :param api_factory: the instance of the web assistant factory to be used when doing requests to the server. If no instance is provided then a new one will be created. :param throttler: the instance of the throttler to use to limit request to the server. If it is not specified the function will create a new one. :return: Dictionary of associations between token pair and its latest price """ result = {} throttler = throttler or AscendExAPIOrderBookDataSource._get_throttler_instance( ) for trading_pair in trading_pairs: api_factory = api_factory or build_api_factory(throttler=throttler) throttler = throttler or cls._get_throttler_instance() rest_assistant = await api_factory.get_rest_assistant() url = f"{CONSTANTS.REST_URL}/{CONSTANTS.TRADES_PATH_URL}"\ f"?symbol={await AscendExAPIOrderBookDataSource.exchange_symbol_associated_to_pair(trading_pair)}" request = RESTRequest(method=RESTMethod.GET, url=url) async with throttler.execute_task(CONSTANTS.TRADES_PATH_URL): resp: RESTResponse = await rest_assistant.call(request=request) if resp.status != 200: raise IOError( f"Error fetching last traded prices at {CONSTANTS.EXCHANGE_NAME}. " f"HTTP status is {resp.status}.") resp_json = await resp.json() if resp_json.get("code") != 0: raise IOError( f"Error fetching last traded prices at {CONSTANTS.EXCHANGE_NAME}. " f"Error is {resp_json.message}.") trades = resp_json.get("data").get("data") # last trade is the most recent trade for trade in trades[-1:]: result[trading_pair] = float(trade.get("p")) return result
async def get_last_traded_prices(cls, trading_pairs: List[str]) -> Dict[str, float]: api_factory = build_api_factory() rest_assistant = await api_factory.get_rest_assistant() url = CONSTANTS.REST_URL + CONSTANTS.TICKER_URL request = RESTRequest(method=RESTMethod.GET, url=url) response: RESTResponse = await rest_assistant.call(request=request) results = dict() resp_json = await response.json() for trading_pair in trading_pairs: resp_record = [o for o in resp_json["data"] if o["symbol"] == convert_to_exchange_trading_pair(trading_pair)][0] results[trading_pair] = float(resp_record["close"]) return results
async def get_snapshot(self, trading_pair: str) -> Dict[str, Any]: rest_assistant = await self._get_rest_assistant() url = CONSTANTS.REST_URL + CONSTANTS.DEPTH_URL # when type is set to "step0", the default value of "depth" is 150 params: Dict = {"symbol": convert_to_exchange_trading_pair(trading_pair), "type": "step0"} request = RESTRequest(method=RESTMethod.GET, url=url, params=params) response: RESTResponse = await rest_assistant.call(request=request) if response.status != 200: raise IOError(f"Error fetching Huobi market snapshot for {trading_pair}. " f"HTTP status is {response.status}.") snapshot_data: Dict[str, Any] = await response.json() return snapshot_data
def test_rest_authenticate_no_parameters_provided(self, mock_ts): mock_ts.return_value = MOCK_TS mock_path = "/TEST_PATH_URL" payload = 'GET' + mock_path + str(int(MOCK_TS) + EXPIRATION) request: RESTRequest = RESTRequest(method=RESTMethod.GET, url=mock_path, is_auth_required=True) signed_request: RESTRequest = self.async_run_with_timeout( self.auth.rest_authenticate(request)) self.assertIn("api-key", signed_request.headers) self.assertEqual(signed_request.headers["api-key"], self.api_key) self.assertIn("api-signature", signed_request.headers) self.assertEqual(signed_request.headers["api-signature"], self._get_signature_from_test_payload(payload))
def test_rest_authenticate_data_provided(self): request: RESTRequest = RESTRequest(method=RESTMethod.POST, url="/TEST_PATH_URL", data=copy.deepcopy( self.test_params), is_auth_required=True) signed_request: RESTRequest = self.async_run_with_timeout( self.auth.rest_authenticate(request)) self.assertIn("X-MBX-APIKEY", signed_request.headers) self.assertEqual(signed_request.headers["X-MBX-APIKEY"], self.api_key) self.assertIn("signature", signed_request.data) self.assertEqual(signed_request.data["signature"], self._get_signature_from_test_payload())