コード例 #1
0
    def test_fetch_trading_pairs(self, mock_api, mock_utils):
        # Mocks binance_utils for BinanceOrderBook.diff_message_from_exchange()
        mock_utils.return_value = self.trading_pair
        url = utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL,
                                    domain=self.domain)
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))

        mock_response: Dict[str, Any] = {
            # Truncated Response
            "symbols": [
                {
                    "symbol": self.ex_trading_pair,
                    "status": "TRADING",
                    "baseAsset": self.base_asset,
                    "quoteAsset": self.quote_asset,
                },
            ]
        }

        mock_api.get(regex_url, body=ujson.dumps(mock_response))

        result: Dict[str] = self.ev_loop.run_until_complete(
            self.data_source.fetch_trading_pairs())

        self.assertEqual(1, len(result))
        self.assertTrue(self.trading_pair in result)
コード例 #2
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
コード例 #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
コード例 #4
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
    def test_get_all_mid_prices(self, mock_api):
        url = utils.public_rest_url(
            path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL,
            domain=self.domain)

        mock_response: List[Dict[str, Any]] = [
            {
                # Truncated Response
                "symbol": self.ex_trading_pair,
                "bidPrice": "99",
                "askPrice": "101",
            },
            {
                # Truncated Response for unrecognized pair
                "symbol": "BCCBTC",
                "bidPrice": "99",
                "askPrice": "101",
            }
        ]

        mock_api.get(url, body=json.dumps(mock_response))

        result: Dict[str, float] = self.async_run_with_timeout(
            self.data_source.get_all_mid_prices())

        self.assertEqual(1, len(result))
        self.assertEqual(100, result[self.trading_pair])
    async def get_snapshot(
            trading_pair: str,
            limit: int = 1000,
            domain: str = "com",
            throttler: Optional[AsyncThrottler] = None) -> Dict[str, Any]:
        throttler = throttler or BinanceAPIOrderBookDataSource._get_throttler_instance(
        )
        params: Dict = {"limit": str(limit), "symbol": binance_utils.convert_to_exchange_trading_pair(trading_pair)} if limit != 0 \
            else {"symbol": binance_utils.convert_to_exchange_trading_pair(trading_pair)}
        async with aiohttp.ClientSession() as client:
            async with throttler.execute_task(
                    limit_id=CONSTANTS.SNAPSHOT_PATH_URL):
                url = binance_utils.public_rest_url(
                    path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=domain)
                async with client.get(url, params=params) as response:
                    response: aiohttp.ClientResponse = response
                    if response.status != 200:
                        raise IOError(
                            f"Error fetching market snapshot for {trading_pair}. "
                            f"Response: {response}.")
                    data: Dict[str, Any] = await response.json()

                    # Need to add the symbol into the snapshot message for the Kafka message queue.
                    # Because otherwise, there'd be no way for the receiver to know which market the
                    # snapshot belongs to.

                    return data
コード例 #7
0
    def test_get_snapshot_catch_exception(self, mock_api):
        url = utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL,
                                    domain=self.domain)
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))

        mock_api.get(regex_url, status=400)
        with self.assertRaises(IOError):
            self.ev_loop.run_until_complete(
                self.data_source.get_snapshot(self.trading_pair))
コード例 #8
0
    def test_get_snapshot_successful(self, mock_api):
        url = utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL,
                                    domain=self.domain)
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))

        mock_api.get(regex_url, body=ujson.dumps(self._snapshot_response()))

        result: Dict[str, Any] = self.ev_loop.run_until_complete(
            self.data_source.get_snapshot(self.trading_pair))

        self.assertEqual(self._snapshot_response(), result)
コード例 #9
0
    def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot(
            self, mock_api):
        url = utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL,
                                    domain=self.domain)
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))

        mock_api.get(regex_url, exception=asyncio.CancelledError)

        with self.assertRaises(asyncio.CancelledError):
            self.ev_loop.run_until_complete(
                self.data_source.listen_for_order_book_snapshots(
                    self.ev_loop, asyncio.Queue()))
コード例 #10
0
    def test_fetch_trading_pairs_exception_raised(self, mock_api):
        BinanceAPIOrderBookDataSource._trading_pair_symbol_map = {}

        url = utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL,
                                    domain=self.domain)
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))

        mock_api.get(regex_url, exception=Exception)

        result: Dict[str] = self.async_run_with_timeout(
            self.data_source.fetch_trading_pairs())

        self.assertEqual(0, len(result))
コード例 #11
0
    def test_fetch_trading_pairs_exception_raised(self, mock_api, mock_utils):
        # Mocks binance_utils for BinanceOrderBook.diff_message_from_exchange()
        mock_utils.return_value = self.trading_pair
        url = utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL,
                                    domain=self.domain)
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))

        mock_api.get(regex_url, exception=Exception)

        result: Dict[str] = self.ev_loop.run_until_complete(
            self.data_source.fetch_trading_pairs())

        self.assertEqual(0, len(result))
コード例 #12
0
    def test_get_new_order_book(self, mock_api):
        url = utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL,
                                    domain=self.domain)
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))

        mock_response: Dict[str, Any] = {
            "lastUpdateId": 1,
            "bids": [["4.00000000", "431.00000000"]],
            "asks": [["4.00000200", "12.00000000"]]
        }
        mock_api.get(regex_url, body=ujson.dumps(mock_response))

        result: OrderBook = self.ev_loop.run_until_complete(
            self.data_source.get_new_order_book(self.trading_pair))

        self.assertEqual(1, result.snapshot_uid)
コード例 #13
0
 async def get_last_traded_price(
         cls,
         trading_pair: str,
         domain: str = "com",
         throttler: Optional[AsyncThrottler] = None) -> float:
     throttler = throttler or cls._get_throttler_instance()
     async with aiohttp.ClientSession() as client:
         async with throttler.execute_task(
                 limit_id=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL):
             url = binance_utils.public_rest_url(
                 path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL,
                 domain=domain)
             resp = await client.get(
                 f"{url}?symbol={binance_utils.convert_to_exchange_trading_pair(trading_pair)}"
             )
             resp_json = await resp.json()
             return float(resp_json["lastPrice"])
コード例 #14
0
    async def _get_last_traded_price(cls, trading_pair: str, domain: str,
                                     rest_assistant: RESTAssistant,
                                     throttler: AsyncThrottler) -> float:

        url = binance_utils.public_rest_url(
            path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL, domain=domain)
        symbol = await cls.exchange_symbol_associated_to_pair(
            trading_pair=trading_pair, domain=domain, throttler=throttler)
        request = RESTRequest(method=RESTMethod.GET,
                              url=f"{url}?symbol={symbol}")

        async with throttler.execute_task(
                limit_id=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL):
            resp: RESTResponse = await rest_assistant.call(request=request)
            if resp.status == 200:
                resp_json = await resp.json()
                return float(resp_json["lastPrice"])
コード例 #15
0
    async def _api_request(self,
                           method: RESTMethod,
                           path_url: str,
                           params: Optional[Dict[str, Any]] = None,
                           data: Optional[Dict[str, Any]] = None,
                           is_auth_required: bool = False) -> Dict[str, Any]:

        headers = {
            "Content-Type":
            "application/json" if method == RESTMethod.POST else
            "application/x-www-form-urlencoded"
        }
        client = await self._get_rest_assistant()

        if is_auth_required:
            url = binance_utils.private_rest_url(path_url, domain=self._domain)
        else:
            url = binance_utils.public_rest_url(path_url, domain=self._domain)

        request = RESTRequest(method=method,
                              url=url,
                              data=data,
                              params=params,
                              headers=headers,
                              is_auth_required=is_auth_required)

        async with self._throttler.execute_task(limit_id=path_url):
            response = await client.call(request)

            if response.status != 200:
                data = await response.text()
                raise IOError(
                    f"Error fetching data from {url}. HTTP status is {response.status} ({data})."
                )
            try:
                parsed_response = await response.json()
            except Exception:
                raise IOError(f"Error parsing data from {response}.")

            if "code" in parsed_response and "msg" in parsed_response:
                raise IOError(
                    f"The request to Binance failed. Error: {parsed_response}. Request: {request}"
                )

        return parsed_response
コード例 #16
0
 async def get_all_mid_prices(domain="com") -> Optional[Decimal]:
     throttler = BinanceAPIOrderBookDataSource._get_throttler_instance()
     async with aiohttp.ClientSession() as client:
         async with throttler.execute_task(
                 limit_id=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL):
             url = binance_utils.public_rest_url(
                 path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL,
                 domain=domain)
             resp = await client.get(url)
             resp_json = await resp.json()
             ret_val = {}
             for record in resp_json:
                 pair = binance_utils.convert_from_exchange_trading_pair(
                     record["symbol"])
                 ret_val[pair] = (
                     Decimal(record.get("bidPrice", "0")) +
                     Decimal(record.get("askPrice", "0"))) / Decimal("2")
             return ret_val
コード例 #17
0
    def test_listen_for_order_book_snapshots_successful(
        self,
        mock_api,
    ):
        msg_queue: asyncio.Queue = asyncio.Queue()
        url = utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL,
                                    domain=self.domain)
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))

        mock_api.get(regex_url, body=json.dumps(self._snapshot_response()))

        self.listening_task = self.ev_loop.create_task(
            self.data_source.listen_for_order_book_snapshots(
                self.ev_loop, msg_queue))

        msg: OrderBookMessage = self.async_run_with_timeout(msg_queue.get())

        self.assertTrue(12345, msg.update_id)
コード例 #18
0
    def test_get_last_trade_prices(self, mock_api):
        url = utils.public_rest_url(
            path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL,
            domain=self.domain)
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))

        mock_response: Dict[str, Any] = {
            # Truncated Response
            "lastPrice": "100",
        }

        mock_api.get(regex_url, body=ujson.dumps(mock_response))

        result: Dict[str, float] = self.ev_loop.run_until_complete(
            self.data_source.get_last_traded_prices(
                trading_pairs=[self.trading_pair], throttler=self.throttler))

        self.assertEqual(1, len(result))
        self.assertEqual(100, result[self.trading_pair])
コード例 #19
0
    def test_get_last_trade_prices(self, mock_api):
        url = utils.public_rest_url(
            path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL,
            domain=self.domain)
        url = f"{url}?symbol={self.base_asset}{self.quote_asset}"
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))

        mock_response = {
            "symbol": "BNBBTC",
            "priceChange": "-94.99999800",
            "priceChangePercent": "-95.960",
            "weightedAvgPrice": "0.29628482",
            "prevClosePrice": "0.10002000",
            "lastPrice": "100.0",
            "lastQty": "200.00000000",
            "bidPrice": "4.00000000",
            "bidQty": "100.00000000",
            "askPrice": "4.00000200",
            "askQty": "100.00000000",
            "openPrice": "99.00000000",
            "highPrice": "100.00000000",
            "lowPrice": "0.10000000",
            "volume": "8913.30000000",
            "quoteVolume": "15.30000000",
            "openTime": 1499783499040,
            "closeTime": 1499869899040,
            "firstId": 28385,
            "lastId": 28460,
            "count": 76,
        }

        mock_api.get(regex_url, body=json.dumps(mock_response))

        result: Dict[str, float] = self.async_run_with_timeout(
            self.data_source.get_last_traded_prices(
                trading_pairs=[self.trading_pair], throttler=self.throttler))

        self.assertEqual(1, len(result))
        self.assertEqual(100, result[self.trading_pair])
コード例 #20
0
    def test_listen_for_order_book_snapshots_log_exception(self, mock_api):
        msg_queue: asyncio.Queue = asyncio.Queue()

        url = utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL,
                                    domain=self.domain)
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))

        mock_api.get(regex_url, exception=Exception)

        self.listening_task = self.ev_loop.create_task(
            self.data_source.listen_for_order_book_snapshots(
                self.ev_loop, msg_queue))
        with self.assertRaises(asyncio.TimeoutError):
            self.ev_loop.run_until_complete(
                asyncio.wait_for(self.listening_task, 1))

        self.assertTrue(
            self._is_logged(
                "ERROR",
                f"Unexpected error fetching order book snapshot for {self.trading_pair}."
            ))
コード例 #21
0
    def test_get_last_price(self, mock_api):
        BinanceAPIOrderBookDataSource._trading_pair_symbol_map = {
            "com":
            bidict({f"{self.binance_ex_trading_pair}": self.trading_pair})
        }

        url = utils.public_rest_url(
            path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL)
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))
        mock_response: Dict[str, Any] = {
            # truncated response
            "symbol": self.binance_ex_trading_pair,
            "lastPrice": "1",
        }
        mock_api.get(regex_url, body=ujson.dumps(mock_response))

        result = self.async_run_with_timeout(
            market_price.get_last_price(exchange="binance",
                                        trading_pair=self.trading_pair))

        self.assertEqual(result, Decimal("1.0"))
コード例 #22
0
    def test_listen_for_order_book_snapshots_log_exception(
            self, mock_api, sleep_mock):
        msg_queue: asyncio.Queue = asyncio.Queue()
        sleep_mock.side_effect = lambda _: self._create_exception_and_unlock_test_with_event(
            asyncio.CancelledError())

        url = utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL,
                                    domain=self.domain)
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))

        mock_api.get(regex_url, exception=Exception)

        self.listening_task = self.ev_loop.create_task(
            self.data_source.listen_for_order_book_snapshots(
                self.ev_loop, msg_queue))
        self.async_run_with_timeout(self.resume_test_event.wait())

        self.assertTrue(
            self._is_logged(
                "ERROR",
                f"Unexpected error fetching order book snapshot for {self.trading_pair}."
            ))
コード例 #23
0
    def test_get_all_mid_prices(self, mock_api, mock_utils):
        # Mocks binance_utils for BinanceOrderBook.diff_message_from_exchange()
        mock_utils.return_value = self.trading_pair
        url = utils.public_rest_url(
            path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL,
            domain=self.domain)
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))

        mock_response: List[Dict[str, Any]] = [{
            # Truncated Response
            "symbol": self.ex_trading_pair,
            "bidPrice": "99",
            "askPrice": "101",
        }]

        mock_api.get(regex_url, body=ujson.dumps(mock_response))

        result: Dict[str, float] = self.ev_loop.run_until_complete(
            self.data_source.get_all_mid_prices())

        self.assertEqual(1, len(result))
        self.assertEqual(100, result[self.trading_pair])
コード例 #24
0
    async def fetch_trading_pairs(domain="com") -> List[str]:
        try:
            throttler = BinanceAPIOrderBookDataSource._get_throttler_instance()
            async with aiohttp.ClientSession() as client:
                async with throttler.execute_task(
                        limit_id=CONSTANTS.EXCHANGE_INFO_PATH_URL):
                    url = binance_utils.public_rest_url(
                        path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL,
                        domain=domain)
                    async with client.get(url, timeout=10) as response:
                        if response.status == 200:
                            data = await response.json()
                            # fetch d["symbol"] for binance us/com
                            raw_trading_pairs = [
                                d["symbol"] for d in data["symbols"]
                                if d["status"] == "TRADING"
                            ]
                            trading_pair_targets = [
                                f"{d['baseAsset']}-{d['quoteAsset']}"
                                for d in data["symbols"]
                                if d["status"] == "TRADING"
                            ]
                            trading_pair_list: List[str] = []
                            for raw_trading_pair, pair_target in zip(
                                    raw_trading_pairs, trading_pair_targets):
                                trading_pair: Optional[
                                    str] = binance_utils.convert_from_exchange_trading_pair(
                                        raw_trading_pair)
                                if trading_pair is not None and trading_pair == pair_target:
                                    trading_pair_list.append(trading_pair)
                            return trading_pair_list

        except Exception:
            # Do nothing if the request fails -- there will be no autocomplete for binance trading pairs
            pass

        return []
コード例 #25
0
    def test_get_binance_mid_price(self, mock_api):
        BinanceAPIOrderBookDataSource._trading_pair_symbol_map = {
            "com":
            bidict({f"{self.base_asset}{self.quote_asset}": self.trading_pair})
        }

        url = utils.public_rest_url(
            path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL)
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))
        mock_response: List[Dict[str, Any]] = [
            {
                # Truncated Response
                "symbol": self.binance_ex_trading_pair,
                "bidPrice": "1.0",
                "askPrice": "2.0",
            },
        ]
        mock_api.get(regex_url, body=ujson.dumps(mock_response))

        result = self.async_run_with_timeout(
            market_price.get_binance_mid_price(trading_pair=self.trading_pair))

        self.assertEqual(result, Decimal("1.5"))
コード例 #26
0
 def test_public_rest_url(self):
     path_url = "/TEST_PATH"
     domain = "com"
     expected_url = CONSTANTS.REST_URL.format(
         domain) + CONSTANTS.PUBLIC_API_VERSION + path_url
     self.assertEqual(expected_url, utils.public_rest_url(path_url, domain))
コード例 #27
0
    def test_fetch_trading_pairs(self, mock_api):
        BinanceAPIOrderBookDataSource._trading_pair_symbol_map = {}
        url = utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL,
                                    domain=self.domain)

        mock_response: Dict[str, Any] = {
            "timezone":
            "UTC",
            "serverTime":
            1639598493658,
            "rateLimits": [],
            "exchangeFilters": [],
            "symbols": [
                {
                    "symbol":
                    "ETHBTC",
                    "status":
                    "TRADING",
                    "baseAsset":
                    "ETH",
                    "baseAssetPrecision":
                    8,
                    "quoteAsset":
                    "BTC",
                    "quotePrecision":
                    8,
                    "quoteAssetPrecision":
                    8,
                    "baseCommissionPrecision":
                    8,
                    "quoteCommissionPrecision":
                    8,
                    "orderTypes": [
                        "LIMIT", "LIMIT_MAKER", "MARKET", "STOP_LOSS_LIMIT",
                        "TAKE_PROFIT_LIMIT"
                    ],
                    "icebergAllowed":
                    True,
                    "ocoAllowed":
                    True,
                    "quoteOrderQtyMarketAllowed":
                    True,
                    "isSpotTradingAllowed":
                    True,
                    "isMarginTradingAllowed":
                    True,
                    "filters": [],
                    "permissions": ["SPOT", "MARGIN"]
                },
                {
                    "symbol":
                    "LTCBTC",
                    "status":
                    "TRADING",
                    "baseAsset":
                    "LTC",
                    "baseAssetPrecision":
                    8,
                    "quoteAsset":
                    "BTC",
                    "quotePrecision":
                    8,
                    "quoteAssetPrecision":
                    8,
                    "baseCommissionPrecision":
                    8,
                    "quoteCommissionPrecision":
                    8,
                    "orderTypes": [
                        "LIMIT", "LIMIT_MAKER", "MARKET", "STOP_LOSS_LIMIT",
                        "TAKE_PROFIT_LIMIT"
                    ],
                    "icebergAllowed":
                    True,
                    "ocoAllowed":
                    True,
                    "quoteOrderQtyMarketAllowed":
                    True,
                    "isSpotTradingAllowed":
                    True,
                    "isMarginTradingAllowed":
                    True,
                    "filters": [],
                    "permissions": ["SPOT", "MARGIN"]
                },
                {
                    "symbol":
                    "BNBBTC",
                    "status":
                    "TRADING",
                    "baseAsset":
                    "BNB",
                    "baseAssetPrecision":
                    8,
                    "quoteAsset":
                    "BTC",
                    "quotePrecision":
                    8,
                    "quoteAssetPrecision":
                    8,
                    "baseCommissionPrecision":
                    8,
                    "quoteCommissionPrecision":
                    8,
                    "orderTypes": [
                        "LIMIT", "LIMIT_MAKER", "MARKET", "STOP_LOSS_LIMIT",
                        "TAKE_PROFIT_LIMIT"
                    ],
                    "icebergAllowed":
                    True,
                    "ocoAllowed":
                    True,
                    "quoteOrderQtyMarketAllowed":
                    True,
                    "isSpotTradingAllowed":
                    True,
                    "isMarginTradingAllowed":
                    True,
                    "filters": [],
                    "permissions": ["MARGIN"]
                },
            ]
        }

        mock_api.get(url, body=json.dumps(mock_response))

        result: Dict[str] = self.async_run_with_timeout(
            self.data_source.fetch_trading_pairs())

        self.assertEqual(2, len(result))
        self.assertIn("ETH-BTC", result)
        self.assertIn("LTC-BTC", result)
        self.assertNotIn("BNB-BTC", result)