def test_update_trading_rules(self, mock_api): self.exchange._trading_pairs.append("XBT-USDT") 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": "COINALPHA_HBOT", "rootSymbol": "COINALPHA", "quoteCurrency": "HBOT", "settlCurrency": "HBOT", "lotSize": 1.0, "tickSize": 0.0001, "minProvideSize": 0.001, "maxOrderQty": 1000000 }, { "symbol": "XBT_USDT", "rootSymbol": "XBT", "quoteCurrency": "USDT", "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": "COINALPHA_HBOT", "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": "XBT_USDT", "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-USDT', Decimal('0.00001'), Decimal('10000')) self.assertEqual(quant_amount, Decimal('0.000010')) 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'), Decimal('10000')) self.assertEqual(quant_amount, Decimal('0.000010')) self.exchange._trading_pairs.remove("XBT-USDT")
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': 'ETH_USDT', '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_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_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")) 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_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, price=Decimal("1010"))) self.assertTrue("OID1" not in self.exchange._in_flight_orders)
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_init_trading_pair_symbols_failure(self, mock_api): BitmexAPIOrderBookDataSource._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": "ETH_USDT", "rootSymbol": "ETH", "quoteCurrency": "USDT" }, ] 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": "ETH_USDT", "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_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")) order = [{ "clOrdID": "OID1", "leavesQty": 500000000, "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(BitmexOrderStatus.PartiallyFilled, in_flight_orders["OID1"].state) # 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_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_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': 'ETH_USDT', '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_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")) 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")) 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_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_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")) tracked_order = self.exchange.in_flight_orders.get("OID1") tracked_order.state = BitmexOrderStatus.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_get_trading_pair_multipliers(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": "COINALPHA_HBOT", "rootSymbol": "COINALPHA", "quoteCurrency": "HBOT", "settlCurrency": "HBOT", "lotSize": 1.0, "tickSize": 0.0001, "minProvideSize": 0.001, "maxOrderQty": 1000000, "underlyingToPositionMultiplier": 100, "quoteToSettleMultiplier": 1000, "positionCurrency": "COINALPHA" }, ] mock_api.get(regex_url, status=200, body=json.dumps(mock_response)) tp_multipliers = self._ev_loop.run_until_complete( utils.get_trading_pair_multipliers("COINALPHA_HBOT")) self.assertTrue(tp_multipliers.base_multiplier > 0)
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': 'ETH_USDT', 'side': 'Sell', 'size': 348, 'price': 3127.4, 'id': 2543 }, { 'symbol': 'ETH_USDT', '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.BASE_URL}{path_url}" self.assertEqual(expected_url, web_utils.rest_url(path_url)) self.assertEqual(expected_url, web_utils.rest_url(path_url))