def test_update_trading_rules(self, mock_api): self.exchange._trading_pairs.append("XBT-USD") url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response: Dict[str, Any] = [ { "symbol": "COINALPHAHBOT", "rootSymbol": "COINALPHA", "quoteCurrency": "HBOT", "settlCurrency": "HBOT", "lotSize": 1.0, "tickSize": 0.0001, "minProvideSize": 0.001, "maxOrderQty": 1000000 }, { "symbol": "XBTUSD", "rootSymbol": "XBT", "quoteCurrency": "USD", "lotSize": 100, "tickSize": 0.5, "settlCurrency": "XBt", "maxOrderQty": 10000000 }, ] mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) url_2 = web_utils.rest_url(CONSTANTS.TICKER_PRICE_URL, domain=self.domain) regex_url_2 = re.compile(f"^{url_2}".replace(".", r"\.").replace( "?", r"\?")) mock_response_2: List[Dict[str, Any]] = [{ "symbol": "COINALPHAHBOT", "lastPrice": 1000.0 }] mock_api.get(regex_url_2, body=json.dumps(mock_response_2)) url_3 = web_utils.rest_url(CONSTANTS.TICKER_PRICE_URL, domain=self.domain) regex_url_3 = re.compile(f"^{url_3}".replace(".", r"\.").replace( "?", r"\?")) mock_response_3: List[Dict[str, Any]] = [{ "symbol": "XBTUSD", "lastPrice": 1000.0 }] mock_api.get(regex_url_3, body=json.dumps(mock_response_3)) self.async_run_with_timeout(self.exchange._update_trading_rules()) self.assertTrue(len(self.exchange._trading_rules) > 0) quant_amount = self.exchange.quantize_order_amount( 'XBT-USD', Decimal('0.00001'), Decimal('10000')) self.assertEqual(quant_amount, Decimal('0')) quant_price = self.exchange.quantize_order_price( 'COINALPHA-HBOT', Decimal('1')) self.assertEqual(quant_price, Decimal('1.0')) quant_amount = self.exchange.quantize_order_amount( 'COINALPHA-HBOT', Decimal('0.00001')) self.assertEqual(quant_amount, Decimal('0')) self.exchange._trading_pairs.remove("XBT-USD")
def test_update_order_status_failure_old_order(self, req_mock, mock_timestamp): self.exchange._last_poll_timestamp = 0 mock_timestamp.return_value = 1 self.exchange.start_tracking_order( order_side=TradeType.SELL, client_order_id="OID1", order_type=OrderType.LIMIT, created_at=0, hash="8886774", trading_pair=self.trading_pair, price=Decimal("10000"), amount=Decimal("1"), leverage=1, position=PositionAction.OPEN, ) order = [{ "clOrdID": "OID2", "leavesQty": 0.5, "ordStatus": "PartiallyFilled", "avgPx": 10001, }] url = web_utils.rest_url(CONSTANTS.ORDER_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) req_mock.get(regex_url, body=json.dumps(order)) self.async_run_with_timeout(self.exchange._update_order_status()) self.assertEqual(len(self.exchange.in_flight_orders), 0)
def test_rest_url_testnet_domain(self): path_url = "/TEST_PATH_URL" expected_url = f"{CONSTANTS.TESTNET_BASE_URL}{path_url}" self.assertEqual( expected_url, web_utils.rest_url(path_url=path_url, domain="testnet"))
def test_closed_account_position_removed_on_stream_event( self, mock_api, ws_connect_mock): url = web_utils.rest_url(CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) positions = self._get_position_risk_api_endpoint_single_position_list() mock_api.get(regex_url, body=json.dumps(positions)) task = self.ev_loop.create_task(self.exchange._update_positions()) self.async_run_with_timeout(task) ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock( ) self.ev_loop.create_task(self.exchange._user_stream_tracker.start()) self.assertEqual(len(self.exchange.account_positions), 1) account_update = self._get_position_update_ws_event_single_position_dict( ) account_update["data"][0]["currentQty"] = 0 self.mocking_assistant.add_websocket_aiohttp_message( ws_connect_mock.return_value, json.dumps(account_update)) self.ev_loop.create_task(self.exchange._user_stream_event_listener()) self.mocking_assistant.run_until_all_aiohttp_messages_delivered( ws_connect_mock.return_value) self.assertEqual(len(self.exchange.account_positions), 0)
def test_listen_for_order_book_snapshots_logs_exception_error_with_response( self, mock_api): url = web_utils.rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = { "m": 1, "i": 2, } mock_api.get(regex_url, body=json.dumps(mock_response), callback=self.resume_test_callback) msg_queue: asyncio.Queue = asyncio.Queue() 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", "Unexpected error occurred fetching orderbook snapshots. Retrying in 5 seconds..." ))
def test_create_order_exception(self, req_mock): url = web_utils.rest_url(CONSTANTS.ORDER_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) create_response = { "updateTime": int(self.start_timestamp), "ordStatus": "Canceled", "orderID": "8886774" } req_mock.post(regex_url, body=json.dumps(create_response)) margin_asset = self.quote_asset mocked_response = self._get_exchange_info_mock_response(margin_asset) trading_rules = self.async_run_with_timeout( self.exchange._format_trading_rules(mocked_response)) self.exchange._trading_rules[self.trading_pair] = trading_rules[0] self.async_run_with_timeout( self.exchange.execute_sell(order_id="OID1", trading_pair=self.trading_pair, amount=Decimal("10000"), order_type=OrderType.LIMIT, position_action=PositionAction.OPEN, price=Decimal("1010"))) self.assertTrue("OID1" not in self.exchange._in_flight_orders)
def test_listen_for_order_book_snapshots_successful(self, mock_api): url = web_utils.rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = [{ 'symbol': 'ETHUSD', 'side': 'Sell', 'size': 348, 'price': 3127.4, 'id': 33337777 }] mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) msg_queue: asyncio.Queue = asyncio.Queue() self.listening_task = self.ev_loop.create_task( self.data_source.listen_for_order_book_snapshots( self.ev_loop, msg_queue)) result = self.async_run_with_timeout(msg_queue.get()) self.assertIsInstance(result, OrderBookMessage) self.assertEqual(OrderBookMessageType.SNAPSHOT, result.type) self.assertTrue(result.has_update_id) self.assertEqual(self.trading_pair, result.content["trading_pair"])
def test_update_balances(self, mock_api): url = web_utils.rest_url(CONSTANTS.ACCOUNT_INFO_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) response = [{ "currency": "USDT", "amount": 100000000, "pendingCredit": 2000000, "pendingDebit": 0 }, { "currency": "XBT", "amount": 1000000000, "pendingCredit": 20000000, "pendingDebit": 0 }] mock_api.get(regex_url, body=json.dumps(response)) url_2 = web_utils.rest_url(CONSTANTS.TOKEN_INFO_URL, domain=self.domain) regex_url_2 = re.compile(f"^{url_2}".replace(".", r"\.").replace( "?", r"\?")) response_2 = [{ "asset": "USDT", "scale": 6 }, { "asset": "XBT", "scale": 8, }] mock_api.get(regex_url_2, body=json.dumps(response_2)) self.async_run_with_timeout(self.exchange._update_balances()) available_balances = self.exchange.available_balances total_balances = self.exchange.get_all_balances() self.assertEqual(Decimal("98"), available_balances["USDT"]) self.assertEqual(Decimal("9.8"), available_balances["XBT"]) self.assertEqual(Decimal("100"), total_balances["USDT"]) self.assertEqual(Decimal("10"), total_balances["XBT"])
def test_update_order_status_successful(self, req_mock, mock_timestamp): self.exchange._last_poll_timestamp = 0 mock_timestamp.return_value = 1 self.exchange.start_tracking_order( order_side=TradeType.SELL, client_order_id="OID1", order_type=OrderType.LIMIT, created_at=time.time(), hash="8886774", trading_pair=self.trading_pair, price=Decimal("10000"), amount=Decimal("1"), leverage=1, position=PositionAction.OPEN, ) order = [{ "clOrdID": "OID1", "leavesQty": 0.5, "ordStatus": "PartiallyFilled", "avgPx": 10001, }] url = web_utils.rest_url(CONSTANTS.ORDER_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) req_mock.get(regex_url, body=json.dumps(order)) self.async_run_with_timeout(self.exchange._update_order_status()) in_flight_orders = self.exchange._in_flight_orders self.assertTrue("OID1" in in_flight_orders) self.assertEqual("OID1", in_flight_orders["OID1"].client_order_id) self.assertEqual(f"{self.base_asset}-{self.quote_asset}", in_flight_orders["OID1"].trading_pair) self.assertEqual(OrderType.LIMIT, in_flight_orders["OID1"].order_type) self.assertEqual(TradeType.SELL, in_flight_orders["OID1"].trade_type) self.assertEqual(10000, in_flight_orders["OID1"].price) self.assertEqual(1, in_flight_orders["OID1"].amount) self.assertEqual(BitmexPerpetualOrderStatus.PartiallyFilled, in_flight_orders["OID1"].state) self.assertEqual(1, in_flight_orders["OID1"].leverage) self.assertEqual(PositionAction.OPEN, in_flight_orders["OID1"].position) # Processing an order update should not impact trade fill information self.assertEqual(Decimal("0.5"), in_flight_orders["OID1"].executed_amount_base) self.assertEqual(Decimal("5000.5"), in_flight_orders["OID1"].executed_amount_quote)
def test_init_trading_pair_symbols_failure(self, mock_api): BitmexPerpetualAPIOrderBookDataSource._trading_pair_symbol_map = {} url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.get(regex_url, status=400, body=json.dumps(["ERROR"])) map = self.async_run_with_timeout( self.data_source.trading_pair_symbol_map(domain=self.domain)) self.assertEqual(0, len(map))
def test_get_snapshot_exception_raised(self, mock_api): url = web_utils.rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.get(regex_url, status=400, body=json.dumps(["ERROR"])) with self.assertRaises(IOError) as context: self.async_run_with_timeout( self.data_source.get_snapshot(trading_pair=self.trading_pair, domain=self.domain)) self.assertEqual( str(context.exception), "Error executing request GET /orderBook/L2. HTTP status is 400. Error: [\"ERROR\"]" )
def test_init_trading_pair_symbols_successful(self, mock_api): url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response: List[Dict[str, Any]] = [ { "symbol": "ETHUSD", "rootSymbol": "ETH", "quoteCurrency": "USD" }, ] mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) self.async_run_with_timeout( self.data_source.init_trading_pair_symbols(domain=self.domain)) self.assertEqual(1, len(self.data_source._trading_pair_symbol_map))
def test_get_last_traded_prices(self, mock_api): url = web_utils.rest_url(CONSTANTS.TICKER_PRICE_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response: List[Dict[str, Any]] = [{ "symbol": "ETHUSD", "lastPrice": 100.0 }] mock_api.get(regex_url, body=json.dumps(mock_response)) result: Dict[str, Any] = self.async_run_with_timeout( self.data_source.get_last_traded_prices( trading_pairs=[self.trading_pair], domain=self.domain)) self.assertTrue(self.trading_pair in result) self.assertEqual(100.0, result[self.trading_pair])
def test_existing_account_position_detected_on_positions_update( self, req_mock): url = web_utils.rest_url(CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) positions = self._get_position_risk_api_endpoint_single_position_list() req_mock.get(regex_url, body=json.dumps(positions)) task = self.ev_loop.create_task(self.exchange._update_positions()) self.async_run_with_timeout(task) self.assertEqual(len(self.exchange.account_positions), 1) pos = list(self.exchange.account_positions.values())[0] self.assertEqual(pos.trading_pair.replace("-", ""), self.symbol)
def test_cancel_all_successful(self, mocked_api, mock_wait): mock_wait.return_value = True url = web_utils.rest_url(CONSTANTS.ORDER_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) cancel_response = {"code": 200, "msg": "success"} mocked_api.delete(regex_url, body=json.dumps(cancel_response)) self.exchange.start_tracking_order( order_side=TradeType.BUY, client_order_id="OID1", order_type=OrderType.LIMIT, created_at=time.time(), hash="8886774", trading_pair=self.trading_pair, price=Decimal("10000"), amount=Decimal("1"), leverage=1, position=PositionAction.CLOSE, ) self.exchange.start_tracking_order( order_side=TradeType.SELL, client_order_id="OID2", order_type=OrderType.LIMIT, created_at=time.time(), hash="8886774", trading_pair=self.trading_pair, price=Decimal("10000"), amount=Decimal("1"), leverage=1, position=PositionAction.OPEN, ) self.assertTrue("OID1" in self.exchange._in_flight_orders) self.assertTrue("OID2" in self.exchange._in_flight_orders) cancellation_results = self.async_run_with_timeout( self.exchange.cancel_all(timeout_seconds=1)) order_cancelled_events = self.order_cancelled_logger.event_log self.assertEqual(0, len(order_cancelled_events)) self.assertEqual(2, len(cancellation_results)) self.assertEqual("OID1", cancellation_results[0].order_id) self.assertEqual("OID2", cancellation_results[1].order_id)
def test_get_snapshot_successful(self, mock_api): url = web_utils.rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = [{ 'symbol': 'ETHUSD', 'side': 'Sell', 'size': 348, 'price': 3127.4 }] mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) result: Dict[str, Any] = self.async_run_with_timeout( self.data_source.get_snapshot(trading_pair=self.trading_pair, domain=self.domain)) self.assertEqual(mock_response, result)
def test_listen_for_order_book_snapshots_cancelled_error_raised( self, mock_api): url = web_utils.rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.get(regex_url, exception=asyncio.CancelledError) msg_queue: asyncio.Queue = asyncio.Queue() with self.assertRaises(asyncio.CancelledError): 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.listening_task) self.assertEqual(0, msg_queue.qsize())
def test_get_funding_info_from_exchange_error_response(self, mock_api): url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_api.get(regex_url, status=400) try: self.async_run_with_timeout( self.data_source._get_funding_info_from_exchange( self.trading_pair)) except Exception: pass self._is_logged( "ERROR", f"Unable to fetch FundingInfo for {self.trading_pair}. Error: None" )
def test_trading_pair_symbol_map_dictionary_not_initialized( self, mock_api): BitmexPerpetualAPIOrderBookDataSource._trading_pair_symbol_map = {} url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response: List[Dict[str, Any]] = [ { "symbol": "ETHUSD", "rootSymbol": "ETH", "quoteCurrency": "USD" }, ] mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) self.async_run_with_timeout( self.data_source.trading_pair_symbol_map(domain=self.domain)) self.assertEqual(1, len(self.data_source._trading_pair_symbol_map))
def test_cancel_unknown_old_order(self, req_mock): url = web_utils.rest_url(CONSTANTS.ORDER_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) cancel_response = { "error": { "message": "order not found", "name": "Not Found" } } req_mock.delete(regex_url, status=400, body=json.dumps(cancel_response)) self.exchange.start_tracking_order( order_side=TradeType.BUY, client_order_id="OID1", order_type=OrderType.LIMIT, created_at=0.0, hash="8886774", trading_pair=self.trading_pair, price=Decimal("10000"), amount=Decimal("1"), leverage=1, position=PositionAction.OPEN, ) tracked_order = self.exchange.in_flight_orders.get("OID1") tracked_order.state = BitmexPerpetualOrderStatus.New self.assertTrue("OID1" in self.exchange._in_flight_orders) try: cancellation_result = self.async_run_with_timeout( self.exchange.cancel_order("OID1")) except Exception: pass self.assertFalse(cancellation_result) self.assertTrue("OID1" not in self.exchange._in_flight_orders)
def test_closed_account_position_removed_on_positions_update( self, req_mock): url = web_utils.rest_url(CONSTANTS.POSITION_INFORMATION_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) positions = self._get_position_risk_api_endpoint_single_position_list() req_mock.get(regex_url, body=json.dumps(positions)) task = self.ev_loop.create_task(self.exchange._update_positions()) self.async_run_with_timeout(task) self.assertEqual(len(self.exchange.account_positions), 1) positions[0]["currentQty"] = "0" req_mock.get(regex_url, body=json.dumps(positions)) task = self.ev_loop.create_task(self.exchange._update_positions()) self.async_run_with_timeout(task) self.assertEqual(len(self.exchange.account_positions), 0)
def test_get_funding_info_from_exchange_successful(self, mock_api): url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = [{ "lastPrice": 1000.0, "fairPrice": 999.1, "fundingRate": 0.1, "fundingTimestamp": "2022-02-11T05:30:30.000Z" }] mock_api.get(regex_url, body=json.dumps(mock_response)) result = self.async_run_with_timeout( self.data_source._get_funding_info_from_exchange( self.trading_pair)) self.assertIsInstance(result, FundingInfo) self.assertEqual(result.trading_pair, self.trading_pair) self.assertEqual(result.rate, Decimal(mock_response[0]["fundingRate"]))
def test_get_trading_pair_size_currency(self, mock_api): url = web_utils.rest_url(CONSTANTS.EXCHANGE_INFO_URL, domain="bitmex_perpetual") regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response: Dict[str, Any] = [ { "symbol": "COINALPHAHBOT", "rootSymbol": "COINALPHA", "quoteCurrency": "HBOT", "settlCurrency": "HBOT", "lotSize": 1.0, "tickSize": 0.0001, "minProvideSize": 0.001, "maxOrderQty": 1000000, "underlyingToPositionMultiplier": 100, "positionCurrency": "COINALPHA" }, ] mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) size_currency = self._ev_loop.run_until_complete( utils.get_trading_pair_size_currency("COINALPHAHBOT")) self.assertTrue(size_currency.is_base)
def test_get_new_order_book(self, mock_api): url = web_utils.rest_url(CONSTANTS.SNAPSHOT_REST_URL, domain=self.domain) regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) mock_response = [{ 'symbol': 'ETHUSD', 'side': 'Sell', 'size': 348, 'price': 3127.4, 'id': 2543 }, { 'symbol': 'ETHUSD', 'side': 'Buy', 'size': 100, 'price': 3000.1, 'id': 2555 }] mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) result = self.async_run_with_timeout( self.data_source.get_new_order_book( trading_pair=self.trading_pair)) self.assertIsInstance(result, OrderBook)
def test_rest_url_main_domain(self): path_url = "/TEST_PATH_URL" expected_url = f"{CONSTANTS.PERPETUAL_BASE_URL}{path_url}" self.assertEqual(expected_url, web_utils.rest_url(path_url)) self.assertEqual(expected_url, web_utils.rest_url(path_url))