async def _ping_listen_key(self) -> bool: rest_assistant = await self._api_factory.get_rest_assistant() try: data = await rest_assistant.execute_request( url=web_utils.public_rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self._domain), params={"listenKey": self._current_listen_key}, method=RESTMethod.PUT, return_err=True, throttler_limit_id=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, headers=self._auth.header_for_authentication()) if "code" in data: self.logger().warning( f"Failed to refresh the listen key {self._current_listen_key}: {data}" ) return False except asyncio.CancelledError: raise except Exception as exception: self.logger().warning( f"Failed to refresh the listen key {self._current_listen_key}: {exception}" ) return False return True
def test_get_last_price(self, mock_api, connector_creator_mock): client_config_map = ClientConfigAdapter(ClientConfigMap()) connector = BinanceExchange( client_config_map, binance_api_key="", binance_api_secret="", trading_pairs=[], trading_required=False) connector._set_trading_pair_symbol_map(bidict({f"{self.binance_ex_trading_pair}": self.trading_pair})) connector_creator_mock.return_value = connector url = web_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"))
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, web_utils.public_rest_url(path_url, domain))
async def _request_order_book_snapshot( self, trading_pair: str) -> 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 :return: the response from the exchange (JSON dictionary) """ params = { "symbol": await self._connector.exchange_symbol_associated_to_pair( trading_pair=trading_pair), "limit": "1000" } rest_assistant = await self._api_factory.get_rest_assistant() data = await rest_assistant.execute_request( url=web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self._domain), params=params, method=RESTMethod.GET, throttler_limit_id=CONSTANTS.SNAPSHOT_PATH_URL, ) return data
def test_get_new_order_book_successful(self, mock_api): url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) resp = self._snapshot_response() mock_api.get(regex_url, body=json.dumps(resp)) order_book: OrderBook = self.async_run_with_timeout( self.data_source.get_new_order_book(self.trading_pair)) expected_update_id = resp["lastUpdateId"] self.assertEqual(expected_update_id, order_book.snapshot_uid) bids = list(order_book.bid_entries()) asks = list(order_book.ask_entries()) self.assertEqual(1, len(bids)) self.assertEqual(4, bids[0].price) self.assertEqual(431, bids[0].amount) self.assertEqual(expected_update_id, bids[0].update_id) self.assertEqual(1, len(asks)) self.assertEqual(4.000002, asks[0].price) self.assertEqual(12, asks[0].amount) self.assertEqual(expected_update_id, asks[0].update_id)
def test_get_snapshot_catch_exception(self, mock_api): url = web_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.async_run_with_timeout( self.data_source.get_snapshot(self.trading_pair))
def test_get_snapshot_successful(self, mock_api): url = web_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())) result: Dict[str, Any] = self.async_run_with_timeout( self.data_source.get_snapshot(self.trading_pair)) self.assertEqual(self._snapshot_response(), result)
def test_listen_for_order_book_snapshots_cancelled_when_fetching_snapshot( self, mock_api): url = web_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.async_run_with_timeout( self.data_source.listen_for_order_book_snapshots( self.ev_loop, asyncio.Queue()))
def test_get_all_mid_prices(self, mock_api): url = web_utils.public_rest_url(CONSTANTS.SERVER_TIME_PATH_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) response = {"serverTime": 1640000003000} mock_api.get(regex_url, body=json.dumps(response)) url = web_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])
def test_fetch_trading_pairs_exception_raised(self, mock_api): BinanceAPIOrderBookDataSource._trading_pair_symbol_map = {} url = web_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( time_synchronizer=self.time_synchronizer)) self.assertEqual(0, len(result))
async def _get_listen_key(self): rest_assistant = await self._api_factory.get_rest_assistant() try: data = await rest_assistant.execute_request( url=web_utils.public_rest_url( path_url=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, domain=self._domain), method=RESTMethod.POST, throttler_limit_id=CONSTANTS.BINANCE_USER_STREAM_PATH_URL, headers=self._auth.header_for_authentication()) except asyncio.CancelledError: raise except Exception as exception: raise IOError( f"Error fetching user stream listen key. Error: {exception}") return data["listenKey"]
def test_get_new_order_book(self, mock_api): url = web_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=json.dumps(mock_response)) result: OrderBook = self.async_run_with_timeout( self.data_source.get_new_order_book(self.trading_pair)) self.assertEqual(1, result.snapshot_uid)
def test_listen_for_order_book_snapshots_successful( self, mock_api, ): msg_queue: asyncio.Queue = asyncio.Queue() url = web_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.assertEqual(1027024, msg.update_id)
def test_get_last_trade_prices(self, mock_api): url = web_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, time_synchronizer=self.time_synchronizer)) self.assertEqual(1, len(result)) self.assertEqual(100, result[self.trading_pair])
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 = web_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"))
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 = web_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}." ))
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 = web_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"))
def all_symbols_url(self): return web_utils.public_rest_url(path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL, domain=self.exchange._domain)
def latest_prices_url(self): url = web_utils.public_rest_url(path_url=CONSTANTS.TICKER_PRICE_CHANGE_PATH_URL, domain=self.exchange._domain) url = f"{url}?symbol={self.exchange_symbol_for_tokens(self.base_asset, self.quote_asset)}" return url
def test_fetch_trading_pairs(self, mock_api): BinanceAPIOrderBookDataSource._trading_pair_symbol_map = {} url = web_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( time_synchronizer=self.time_synchronizer)) self.assertEqual(2, len(result)) self.assertIn("ETH-BTC", result) self.assertIn("LTC-BTC", result) self.assertNotIn("BNB-BTC", result)
def test_fetch_all(self, mock_api, all_connector_settings_mock): all_connector_settings_mock.return_value = { "binance": ConnectorSetting(name='binance', type=ConnectorType.Exchange, example_pair='ZRX-ETH', centralised=True, use_ethereum_wallet=False, trade_fee_schema=TradeFeeSchema( percent_fee_token=None, maker_percent_fee_decimal=Decimal('0.001'), taker_percent_fee_decimal=Decimal('0.001'), buy_percent_fee_deducted_from_returns=False, maker_fixed_fees=[], taker_fixed_fees=[]), config_keys={ 'binance_api_key': ConfigVar(key='binance_api_key', prompt=""), 'binance_api_secret': ConfigVar(key='binance_api_secret', prompt="") }, is_sub_domain=False, parent_name=None, domain_parameter=None, use_eth_gas_lookup=False) } url = binance_web_utils.public_rest_url( path_url=CONSTANTS.EXCHANGE_INFO_PATH_URL) 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)) client_config_map = ClientConfigAdapter(ClientConfigMap()) fetcher = TradingPairFetcher(client_config_map) asyncio.get_event_loop().run_until_complete(fetcher._fetch_task) trading_pairs = fetcher.trading_pairs self.assertEqual(1, len(trading_pairs.keys())) self.assertIn("binance", trading_pairs) binance_pairs = trading_pairs["binance"] self.assertEqual(2, len(binance_pairs)) self.assertIn("ETH-BTC", binance_pairs) self.assertIn("LTC-BTC", binance_pairs) self.assertNotIn("BNB-BTC", binance_pairs)