Exemple #1
0
    def test_get_ms_timestamp(self, time_mock):
        time_mock.return_value = 1234567891.23456

        timestamp = utils.get_ms_timestamp()
        self.assertEqual(1234567891234, timestamp)
        # If requested two times at the same moment it should return the same value (writen this way to ensure
        # we are not using the nonce any more
        timestamp = utils.get_ms_timestamp()
        self.assertEqual(1234567891234, timestamp)
    def test_get_ms_timestamp(self):
        with mock.patch(
                'hummingbot.connector.exchange.ascend_ex.ascend_ex_utils.get_tracking_nonce_low_res'
        ) as get_tracking_nonce_low_res_mock:
            get_tracking_nonce_low_res_mock.return_value = 123456789

            timestamp = utils.get_ms_timestamp()
            self.assertEqual(123456789, timestamp)
    def get_auth_headers(self, path_url: str, data: Dict[str, Any] = None):
        """
        Generates authentication signature and return it in a dictionary along with other inputs
        :return: a dictionary of request info including the request signature
        """

        timestamp = str(get_ms_timestamp())
        message = timestamp + path_url
        signature = hmac.new(self.secret_key.encode('utf-8'),
                             message.encode('utf-8'),
                             hashlib.sha256).hexdigest()

        return {
            "x-auth-key": self.api_key,
            "x-auth-signature": signature,
            "x-auth-timestamp": timestamp,
        }
    async def _execute_cancel(self, trading_pair: str, order_id: str) -> str:
        """
        Executes order cancellation process by first calling cancel-order API. The API result doesn't confirm whether
        the cancellation is successful, it simply states it receives the request.
        :param trading_pair: The market trading pair
        :param order_id: The internal order id
        order.last_state to change to CANCELED
        """
        try:
            tracked_order = self._in_flight_orders.get(order_id)
            if tracked_order is None:
                raise ValueError(
                    f"Failed to cancel order - {order_id}. Order not found.")
            if tracked_order.exchange_order_id is None:
                await tracked_order.get_exchange_order_id()
            ex_order_id = tracked_order.exchange_order_id

            api_params = {
                "symbol":
                ascend_ex_utils.convert_to_exchange_trading_pair(trading_pair),
                "orderId":
                ex_order_id,
                "time":
                ascend_ex_utils.get_ms_timestamp()
            }
            await self._api_request(method="delete",
                                    path_url=CONSTANTS.ORDER_PATH_URL,
                                    data=api_params,
                                    is_auth_required=True,
                                    force_auth_path_url="order")

            return order_id
        except asyncio.CancelledError:
            raise
        except Exception as e:
            if str(e).find("Order not found") != -1:
                self.stop_tracking_order(order_id)
                return

            self.logger().network(
                f"Failed to cancel order {order_id}: {str(e)}",
                exc_info=True,
                app_warning_msg=
                f"Failed to cancel the order {order_id} on AscendEx. "
                f"Check API key and network connection.")
    async def _execute_cancel(self, trading_pair: str, order_id: str) -> str:
        """
        Executes order cancellation process by first calling cancel-order API. The API result doesn't confirm whether
        the cancellation is successful, it simply states it receives the request.
        :param trading_pair: The market trading pair
        :param order_id: The internal order id
        """
        try:
            tracked_order = self._in_flight_order_tracker.fetch_tracked_order(order_id)
            if tracked_order is None:
                non_tracked_order = self._in_flight_order_tracker.fetch_cached_order(order_id)
                if non_tracked_order is None:
                    raise ValueError(f"Failed to cancel order - {order_id}. Order not found.")
                else:
                    self.logger().info(f"The order {order_id} was finished before being cancelled")
            else:
                ex_order_id = await tracked_order.get_exchange_order_id()

                api_params = {
                    "symbol": ascend_ex_utils.convert_to_exchange_trading_pair(trading_pair),
                    "orderId": ex_order_id,
                    "time": ascend_ex_utils.get_ms_timestamp(),
                }
                await self._api_request(
                    method="delete",
                    path_url=CONSTANTS.ORDER_PATH_URL,
                    data=api_params,
                    is_auth_required=True,
                    force_auth_path_url="order",
                )

            return order_id
        except asyncio.CancelledError:
            raise
        except asyncio.TimeoutError:
            self._stop_tracking_order_exceed_no_exchange_id_limit(tracked_order=tracked_order)
        except Exception as e:
            self.logger().error(
                f"Failed to cancel order {order_id}: {str(e)}",
                exc_info=True,
            )
    async def _create_order(self, trade_type: TradeType, order_id: str,
                            trading_pair: str, amount: Decimal,
                            order_type: OrderType, price: Decimal):
        """
        Calls create-order API end point to place an order, starts tracking the order and triggers order created event.
        :param trade_type: BUY or SELL
        :param order_id: Internal order id (aka client_order_id)
        :param trading_pair: The market to place order
        :param amount: The order amount (in base token value)
        :param order_type: The order type
        :param price: The order price
        """
        if not order_type.is_limit_type():
            raise Exception(f"Unsupported order type: {order_type}")
        amount = self.quantize_order_amount(trading_pair, amount)
        price = self.quantize_order_price(trading_pair, price)
        if amount <= s_decimal_0:
            raise ValueError("Order amount must be greater than zero.")
        try:
            timestamp = ascend_ex_utils.get_ms_timestamp()
            api_params = {
                "id":
                order_id,
                "time":
                timestamp,
                "symbol":
                ascend_ex_utils.convert_to_exchange_trading_pair(trading_pair),
                "orderPrice":
                f"{price:f}",
                "orderQty":
                f"{amount:f}",
                "orderType":
                "limit",
                "side":
                "buy" if trade_type == TradeType.BUY else "sell",
                "respInst":
                "ACCEPT",
            }
            self.start_tracking_order(order_id, None, trading_pair, trade_type,
                                      price, amount, order_type)
            resp = await self._api_request(method="post",
                                           path_url=CONSTANTS.ORDER_PATH_URL,
                                           data=api_params,
                                           is_auth_required=True,
                                           force_auth_path_url="order")
            exchange_order_id = str(resp["data"]["info"]["orderId"])
            tracked_order: AscendExInFlightOrder = self._in_flight_orders.get(
                order_id)
            tracked_order.update_exchange_order_id(exchange_order_id)
            if resp["data"]["status"] == "Ack":
                # Ack status means the server has received the request
                return
            tracked_order.update_status(resp["data"]["info"]["status"])
            if tracked_order.is_failure:
                raise Exception(
                    f'Failed to create an order, reason: {resp["data"]["info"]["errorCode"]}'
                )

            self.logger().info(
                f"Created {order_type.name} {trade_type.name} order {order_id} for "
                f"{amount} {trading_pair}.")
            self.trigger_order_created_event(tracked_order)
        except asyncio.CancelledError:
            raise
        except Exception:
            self.stop_tracking_order(order_id)
            msg = f"Error submitting {trade_type.name} {order_type.name} order to AscendEx for " \
                  f"{amount} {trading_pair} " \
                  f"{price}."
            self.logger().network(msg, exc_info=True, app_warning_msg=msg)
            self.trigger_event(
                MarketEvent.OrderFailure,
                MarketOrderFailureEvent(self.current_timestamp, order_id,
                                        order_type))
    async def _create_order(
            self,
            trade_type: TradeType,
            order_id: str,
            trading_pair: str,
            amount: Decimal,
            order_type: OrderType,
            price: Decimal,
    ):
        """
        Calls create-order API end point to place an order, starts tracking the order and triggers order created event.
        :param trade_type: BUY or SELL
        :param order_id: Internal order id (aka client_order_id)
        :param trading_pair: The market to place order
        :param amount: The order amount (in base token value)
        :param order_type: The order type
        :param price: The order price
        """
        if not order_type.is_limit_type():
            raise Exception(f"Unsupported order type: {order_type}")
        amount = self.quantize_order_amount(trading_pair, amount)
        price = self.quantize_order_price(trading_pair, price)
        if amount <= s_decimal_0:
            raise ValueError("Order amount must be greater than zero.")
        try:
            timestamp = ascend_ex_utils.get_ms_timestamp()
            # Order UUID is strictly used to enable AscendEx to construct a unique(still questionable) exchange_order_id
            order_uuid = f"{ascend_ex_utils.HBOT_BROKER_ID}-{ascend_ex_utils.uuid32()}"[:32]
            api_params = {
                "id": order_uuid,
                "time": timestamp,
                "symbol": ascend_ex_utils.convert_to_exchange_trading_pair(trading_pair),
                "orderPrice": f"{price:f}",
                "orderQty": f"{amount:f}",
                "orderType": "limit",
                "side": "buy" if trade_type == TradeType.BUY else "sell",
                "respInst": "ACCEPT",
            }
            self.start_tracking_order(
                order_id=order_id,
                trading_pair=trading_pair,
                trade_type=trade_type,
                price=price,
                amount=amount,
                order_type=order_type,
            )

            try:
                resp = await self._api_request(
                    method="post",
                    path_url=CONSTANTS.ORDER_PATH_URL,
                    data=api_params,
                    is_auth_required=True,
                    force_auth_path_url="order",
                )

                resp_status = resp["data"]["status"].upper()

                order_data = resp["data"]["info"]
                if resp_status == "ACK":
                    # Ack request status means the server has received the request
                    return

                order_update = None
                if resp_status == "ACCEPT":
                    order_update: OrderUpdate = OrderUpdate(
                        client_order_id=order_id,
                        exchange_order_id=str(order_data["orderId"]),
                        trading_pair=trading_pair,
                        update_timestamp=order_data["lastExecTime"] * 1e-3,
                        new_state=OrderState.OPEN,
                    )
                elif resp_status == "DONE":
                    order_update: OrderUpdate = OrderUpdate(
                        client_order_id=order_id,
                        exchange_order_id=str(order_data["orderId"]),
                        trading_pair=trading_pair,
                        update_timestamp=order_data["lastExecTime"] * 1e-3,
                        new_state=CONSTANTS.ORDER_STATE[order_data["status"]],
                    )
                elif resp_status == "ERR":
                    order_update: OrderUpdate = OrderUpdate(
                        client_order_id=order_id,
                        exchange_order_id=str(order_data["orderId"]),
                        trading_pair=trading_pair,
                        update_timestamp=order_data["lastExecTime"] * 1e-3,
                        new_state=OrderState.FAILED,
                    )
                self._in_flight_order_tracker.process_order_update(order_update)
            except IOError:
                self.logger().exception(f"The request to create the order {order_id} failed")
                self.stop_tracking_order(order_id)
        except asyncio.CancelledError:
            raise
        except Exception:
            msg = (f"Error submitting {trade_type.name} {order_type.name} order to AscendEx for "
                   f"{amount} {trading_pair} {price}.")
            self.logger().exception(msg)