Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/kucoin exchange id is none #6999

Merged
6 changes: 6 additions & 0 deletions hummingbot/connector/exchange/kucoin/kucoin_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,9 @@
RateLimit(limit_id=FILLS_PATH_URL, limit=9, time_interval=3),
RateLimit(limit_id=FILLS_PATH_URL_HFT, limit=9, time_interval=3),
]

RET_CODE_OK = 200000
RET_CODE_ORDER_NOT_EXIST_OR_NOT_ALLOW_TO_CANCEL = 400100
RET_MSG_ORDER_NOT_EXIST_OR_NOT_ALLOW_TO_CANCEL = "order_not_exist_or_not_allow_to_cancel"
RET_CODE_RESOURCE_NOT_FOUND = 404
RET_MSG_RESOURCE_NOT_FOUND = "Not Found"
27 changes: 11 additions & 16 deletions hummingbot/connector/exchange/kucoin/kucoin_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,12 @@ def _is_request_exception_related_to_time_synchronizer(self, request_exception:
return False

def _is_order_not_found_during_status_update_error(self, status_update_exception: Exception) -> bool:
# TODO: implement this method correctly for the connector
# The default implementation was added when the functionality to detect not found orders was introduced in the
# ExchangePyBase class. Also fix the unit test test_lost_order_removed_if_not_found_during_order_status_update
# when replacing the dummy implementation
return False
return (str(CONSTANTS.RET_CODE_RESOURCE_NOT_FOUND) in str(status_update_exception) and
str(CONSTANTS.RET_MSG_RESOURCE_NOT_FOUND) in str(status_update_exception))

def _is_order_not_found_during_cancelation_error(self, cancelation_exception: Exception) -> bool:
# TODO: implement this method correctly for the connector
# The default implementation was added when the functionality to detect not found orders was introduced in the
# ExchangePyBase class. Also fix the unit test test_cancel_order_not_found_in_the_exchange when replacing the
# dummy implementation
return False
return (str(CONSTANTS.RET_CODE_ORDER_NOT_EXIST_OR_NOT_ALLOW_TO_CANCEL) in str(cancelation_exception)
and str(CONSTANTS.RET_MSG_ORDER_NOT_EXIST_OR_NOT_ALLOW_TO_CANCEL) in str(cancelation_exception))

def _create_web_assistants_factory(self) -> WebAssistantsFactory:
return web_utils.build_api_factory(
Expand Down Expand Up @@ -221,9 +215,9 @@ async def _place_order(self,
is_auth_required=True,
limit_id=CONSTANTS.POST_ORDER_LIMIT_ID,
)
order_data = exchange_order_id.get("data")
order_id = order_data["orderId"] if order_data else None
return order_id, self.current_timestamp
if exchange_order_id.get("data") is None:
raise IOError(f"Error placing order on Kucoin: {exchange_order_id}")
return str(exchange_order_id["data"]["orderId"]), self.current_timestamp

async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder):
"""
Expand All @@ -238,9 +232,10 @@ async def _place_cancel(self, order_id: str, tracked_order: InFlightOrder):
limit_id=CONSTANTS.DELETE_ORDER_LIMIT_ID
)
response_param = "orderId" if self.domain == "hft" else "cancelledOrderIds"
if tracked_order.exchange_order_id in cancel_result["data"].get(response_param, []):
return True
return False
if cancel_result.get("data") is not None:
return tracked_order.exchange_order_id in cancel_result["data"].get(response_param, [])
else:
raise IOError(f"Error cancelling order on Kucoin: {cancel_result}")

async def _user_stream_event_listener(self):
"""
Expand Down
52 changes: 48 additions & 4 deletions test/hummingbot/connector/exchange/kucoin/test_kucoin_exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ def test_create_limit_order_successfully(self, mock_api):
regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?"))

creation_response = {
"code": "200000",
"data": {
"orderId": "5bd6e9286d99522a52e458de"
}}
Expand Down Expand Up @@ -576,6 +577,7 @@ def test_create_limit_maker_order_successfully(self, mock_api):
regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?"))

creation_response = {
"code": "200000",
"data": {
"orderId": "5bd6e9286d99522a52e458de"
}}
Expand Down Expand Up @@ -622,6 +624,38 @@ def test_create_limit_maker_order_successfully(self, mock_api):
)
)

@aioresponses()
@patch("hummingbot.connector.exchange.kucoin.kucoin_exchange.KucoinExchange.get_price")
def test_create_order_with_wrong_params_raises_io_error(self, mock_api, get_price_mock):
get_price_mock.return_value = Decimal(1000)
self._simulate_trading_rules_initialized()
request_sent_event = asyncio.Event()
self.exchange._set_current_timestamp(1640780000)
url = web_utils.private_rest_url(CONSTANTS.ORDERS_PATH_URL)
regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?"))

creation_response = {
"code": "300000",
"msg": "The quantity is invalid."}

mock_api.post(regex_url,
body=json.dumps(creation_response),
callback=lambda *args, **kwargs: request_sent_event.set())

self._simulate_trading_rules_initialized()

with self.assertRaises(IOError):
asyncio.get_event_loop().run_until_complete(
self.exchange._place_order(
trade_type=TradeType.BUY,
order_id="C1",
trading_pair=self.trading_pair,
amount=Decimal("0"),
order_type=OrderType.LIMIT,
price=Decimal("46000"),
),
)

@aioresponses()
@patch("hummingbot.connector.exchange.kucoin.kucoin_exchange.KucoinExchange.get_price")
def test_create_market_order_successfully(self, mock_api, get_price_mock):
Expand All @@ -633,6 +667,7 @@ def test_create_market_order_successfully(self, mock_api, get_price_mock):
regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?"))

creation_response = {
"code": "200000",
"data": {
"orderId": "5bd6e9286d99522a52e458de"
}}
Expand Down Expand Up @@ -708,7 +743,7 @@ def test_create_order_fails_and_raises_failure_event(self, mock_api):
self.assertEqual(self.exchange.current_timestamp, failure_event.timestamp)
self.assertEqual(OrderType.LIMIT, failure_event.order_type)
self.assertEqual("OID1", failure_event.order_id)

self.assertRaises(IOError)
self.assertTrue(
self._is_logged(
"INFO",
Expand Down Expand Up @@ -777,7 +812,6 @@ def test_create_order_fails_when_trading_rule_error_and_raises_failure_event(sel
def test_cancel_order_successfully(self, mock_api):
request_sent_event = asyncio.Event()
self.exchange._set_current_timestamp(1640780000)

self.exchange.start_tracking_order(
order_id="OID1",
exchange_order_id="4",
Expand All @@ -795,7 +829,12 @@ def test_cancel_order_successfully(self, mock_api):
regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?"))

response = {
"data": {"cancelledOrderIds": [order.exchange_order_id]}
"code": "200000",
"data": {
"cancelledOrderIds": [
order.exchange_order_id
]
}
}

mock_api.delete(regex_url,
Expand Down Expand Up @@ -945,7 +984,12 @@ def test_cancel_two_orders_with_cancel_all_and_one_fails(self, mock_api):
regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?"))

response = {
"data": {"cancelledOrderIds": [order1.exchange_order_id]}
"code": "200000",
"data": {
"cancelledOrderIds": [
order1.exchange_order_id
]
}
}

mock_api.delete(regex_url, body=json.dumps(response))
Expand Down