Exemple #1
0
    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
Exemple #2
0
    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
Exemple #3
0
    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
Exemple #4
0
    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
Exemple #5
0
    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
Exemple #6
0
    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
Exemple #8
0
    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 []
Exemple #9
0
    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
Exemple #10
0
    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"])
Exemple #11
0
    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&param_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)
Exemple #14
0
    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")
Exemple #17
0
 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()
Exemple #18
0
    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")
Exemple #20
0
    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()
Exemple #25
0
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
Exemple #29
0
    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())