Exemple #1
0
    async def _subscribe_to_order_book_streams(self) -> aiohttp.ClientWebSocketResponse:
        try:
            trading_pairs = ",".join([
                convert_to_exchange_trading_pair(trading_pair)
                for trading_pair in self._trading_pairs
            ])
            subscription_payloads = [
                {
                    "op": CONSTANTS.SUB_ENDPOINT_NAME,
                    "ch": f"{topic}:{trading_pairs}"
                }
                for topic in [self.DIFF_TOPIC_ID, self.TRADE_TOPIC_ID]
            ]
            ws = await aiohttp.ClientSession().ws_connect(url=CONSTANTS.WS_URL,
                                                          heartbeat=self.HEARTBEAT_PING_INTERVAL)
            for payload in subscription_payloads:
                async with self._throttler.execute_task(CONSTANTS.SUB_ENDPOINT_NAME):
                    await ws.send_json(payload)

            self.logger().info(f"Subscribed to {self._trading_pairs} orderbook trading and delta streams...")

            return ws
        except asyncio.CancelledError:
            raise
        except Exception:
            self.logger().error("Unexpected error occurred subscribing to order book trading and delta streams...")
            raise
    async def cancel_all(self, timeout_seconds: float):
        """
        Cancels all in-flight orders and waits for cancellation results.
        Used by bot's top level stop and exit commands (cancelling outstanding orders on exit)
        :param timeout_seconds: The timeout at which the operation will be canceled.
        :returns List of CancellationResult which indicates whether each order is successfully cancelled.
        """
        cancellation_results = []
        try:
            tracked_orders: Dict[
                str, AscendExInFlightOrder] = self._in_flight_orders.copy()

            api_params = {
                "orders": [{
                    'id':
                    ascend_ex_utils.uuid32(),
                    "orderId":
                    await order.get_exchange_order_id(),
                    "symbol":
                    ascend_ex_utils.convert_to_exchange_trading_pair(
                        order.trading_pair),
                    "time":
                    int(time.time() * 1e3)
                } for order in tracked_orders.values()]
            }

            await self._api_request(method="delete",
                                    path_url=CONSTANTS.ORDER_BATCH_PATH_URL,
                                    data=api_params,
                                    is_auth_required=True,
                                    force_auth_path_url="order/batch")

            open_orders = await self.get_open_orders()

            for cl_order_id, tracked_order in tracked_orders.items():
                open_order = [
                    o for o in open_orders if o.client_order_id == cl_order_id
                ]
                if not open_order:
                    cancellation_results.append(
                        CancellationResult(cl_order_id, True))
                    self.trigger_event(
                        MarketEvent.OrderCancelled,
                        OrderCancelledEvent(self.current_timestamp,
                                            cl_order_id))
                    self.stop_tracking_order(cl_order_id)
                else:
                    cancellation_results.append(
                        CancellationResult(cl_order_id, False))
        except Exception:
            self.logger().network(
                "Failed to cancel all orders.",
                exc_info=True,
                app_warning_msg=
                "Failed to cancel all orders on AscendEx. Check API key and network connection."
            )
        return cancellation_results
    def _process_order_message(self, order_msg: AscendExOrder):
        """
        Updates in-flight order and triggers cancellation or failure event if needed.
        :param order_msg: The order response from either REST or web socket API (they are of the same format)
        """
        tracked_order = self._in_flight_order_tracker.fetch_order(exchange_order_id=order_msg.orderId)

        if tracked_order is not None:
            order_status = CONSTANTS.ORDER_STATE[order_msg.status]
            cumulative_filled_amount = Decimal(order_msg.cumFilledQty)
            if (order_status in [OrderState.PARTIALLY_FILLED, OrderState.FILLED]
                    and cumulative_filled_amount > tracked_order.executed_amount_base):
                filled_amount = cumulative_filled_amount - tracked_order.executed_amount_base
                cumulative_fee = Decimal(order_msg.cumFee)
                fee_already_paid = tracked_order.cumulative_fee_paid(token=order_msg.feeAsset, exchange=self)
                if cumulative_fee > fee_already_paid:
                    fee = TradeFeeBase.new_spot_fee(
                        fee_schema=self.trade_fee_schema(),
                        trade_type=tracked_order.trade_type,
                        percent_token=order_msg.feeAsset,
                        flat_fees=[TokenAmount(amount=cumulative_fee - fee_already_paid, token=order_msg.feeAsset)]
                    )
                else:
                    fee = TradeFeeBase.new_spot_fee(
                        fee_schema=self.trade_fee_schema(),
                        trade_type=tracked_order.trade_type)
                trade_update = TradeUpdate(
                    trade_id=str(order_msg.lastExecTime),
                    client_order_id=tracked_order.client_order_id,
                    exchange_order_id=tracked_order.exchange_order_id,
                    trading_pair=tracked_order.trading_pair,
                    fee=fee,
                    fill_base_amount=filled_amount,
                    fill_quote_amount=filled_amount * Decimal(order_msg.avgPx),
                    fill_price=Decimal(order_msg.avgPx),
                    fill_timestamp=int(order_msg.lastExecTime),
                )
                self._in_flight_order_tracker.process_trade_update(trade_update)

            order_update = OrderUpdate(
                exchange_order_id=order_msg.orderId,
                trading_pair=ascend_ex_utils.convert_to_exchange_trading_pair(order_msg.symbol),
                update_timestamp=order_msg.lastExecTime * 1e-3,
                new_state=order_status,
            )

            self._in_flight_order_tracker.process_order_update(order_update=order_update)
Exemple #4
0
    async def listen_for_order_book_diffs(self, ev_loop: asyncio.BaseEventLoop,
                                          output: asyncio.Queue):
        while True:
            try:
                trading_pairs = ",".join(
                    list(
                        map(
                            lambda trading_pair:
                            convert_to_exchange_trading_pair(trading_pair),
                            self._trading_pairs)))
                ch = f"depth:{trading_pairs}"
                payload = {"op": CONSTANTS.SUB_ENDPOINT_NAME, "ch": ch}

                async with websockets.connect(CONSTANTS.WS_URL) as ws:
                    ws: websockets.WebSocketClientProtocol = ws
                    async with self._throttler.execute_task(
                            CONSTANTS.SUB_ENDPOINT_NAME):
                        await ws.send(ujson.dumps(payload))

                    async for raw_msg in self._inner_messages(ws):
                        msg = ujson.loads(raw_msg)
                        if msg is None:
                            continue
                        if msg.get("m", '') == "ping":
                            async with self._throttler.execute_task(
                                    CONSTANTS.PONG_ENDPOINT_NAME):
                                await ws.send(
                                    ujson.dumps(
                                        dict(op=CONSTANTS.PONG_ENDPOINT_NAME)))
                        if msg.get("m", '') == "depth":
                            msg_timestamp: int = msg.get("data").get("ts")
                            trading_pair: str = convert_from_exchange_trading_pair(
                                msg.get("symbol"))
                            order_book_message: OrderBookMessage = AscendExOrderBook.diff_message_from_exchange(
                                msg.get("data"),
                                msg_timestamp,
                                metadata={"trading_pair": trading_pair})
                            output.put_nowait(order_book_message)
            except asyncio.CancelledError:
                raise
            except Exception as e:
                self.logger().debug(str(e))
                self.logger().error(
                    "Unexpected error with WebSocket connection. Retrying after 30 seconds...",
                    exc_info=True)
                await asyncio.sleep(30.0)
    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 cancel_all(self, timeout_seconds: float):
        """
        Cancels all in-flight orders and waits for cancellation results.
        Used by bot's top level stop and exit commands (cancelling outstanding orders on exit)
        :param timeout_seconds: The timeout at which the operation will be canceled.
        :returns List of CancellationResult which indicates whether each order is successfully cancelled.
        """
        order_ids_to_cancel = []
        cancel_payloads = []
        successful_cancellations = []
        failed_cancellations = []

        for order in filter(lambda active_order: not active_order.is_done,
                            self._in_flight_order_tracker.active_orders.values()):
            if order.exchange_order_id is not None:
                cancel_payloads.append({
                    "id": ascend_ex_utils.uuid32(),
                    "orderId": order.exchange_order_id,
                    "symbol": ascend_ex_utils.convert_to_exchange_trading_pair(order.trading_pair),
                    "time": int(time.time() * 1e3),
                })
                order_ids_to_cancel.append(order.client_order_id)
            else:
                failed_cancellations.append(CancellationResult(order.client_order_id, False))

        if cancel_payloads:
            try:
                api_params = {"orders": cancel_payloads}
                await self._api_request(
                    method="delete",
                    path_url=CONSTANTS.ORDER_BATCH_PATH_URL,
                    data=api_params,
                    is_auth_required=True,
                    force_auth_path_url="order/batch",
                )

                successful_cancellations = [CancellationResult(order_id, True) for order_id in order_ids_to_cancel]

            except Exception:
                self.logger().network(
                    "Failed to cancel all orders.",
                    exc_info=True,
                    app_warning_msg="Failed to cancel all orders on AscendEx. Check API key and network connection.",
                )
        return successful_cancellations + failed_cancellations
    def _process_order_message(self, order_msg: AscendExOrder):
        """
        Updates in-flight order and triggers cancellation or failure event if needed.
        :param order_msg: The order response from either REST or web socket API (they are of the same format)
        """

        order_update = OrderUpdate(
            exchange_order_id=order_msg.orderId,
            trading_pair=ascend_ex_utils.convert_to_exchange_trading_pair(order_msg.symbol),
            update_timestamp=order_msg.lastExecTime,
            new_state=CONSTANTS.ORDER_STATE[order_msg.status],
            fill_price=Decimal(order_msg.avgPx),
            executed_amount_base=Decimal(order_msg.cumFilledQty),
            executed_amount_quote=Decimal(order_msg.avgPx) * Decimal(order_msg.cumFilledQty),
            fee_asset=order_msg.feeAsset,
            cumulative_fee_paid=Decimal(order_msg.cumFee),
        )

        self._in_flight_order_tracker.process_order_update(order_update=order_update)
Exemple #8
0
    async def listen_for_trades(self, ev_loop: asyncio.BaseEventLoop,
                                output: asyncio.Queue):
        while True:
            try:
                trading_pairs = ",".join(
                    list(
                        map(
                            lambda trading_pair:
                            convert_to_exchange_trading_pair(trading_pair),
                            self._trading_pairs)))
                payload = {"op": "sub", "ch": f"trades:{trading_pairs}"}

                async with websockets.connect(WS_URL) as ws:
                    ws: websockets.WebSocketClientProtocol = ws
                    await ws.send(ujson.dumps(payload))

                    async for raw_msg in self._inner_messages(ws):
                        try:
                            msg = ujson.loads(raw_msg)
                            if (msg is None or msg.get("m") != "trades"):
                                continue

                            trading_pair: str = convert_from_exchange_trading_pair(
                                msg.get("symbol"))

                            for trade in msg.get("data"):
                                trade_timestamp: int = trade.get("ts")
                                trade_msg: OrderBookMessage = AscendExOrderBook.trade_message_from_exchange(
                                    trade,
                                    trade_timestamp,
                                    metadata={"trading_pair": trading_pair})
                                output.put_nowait(trade_msg)
                        except Exception:
                            raise
            except asyncio.CancelledError:
                raise
            except Exception as e:
                self.logger().debug(str(e))
                self.logger().error(
                    "Unexpected error with WebSocket connection. Retrying after 30 seconds...",
                    exc_info=True)
                await asyncio.sleep(30.0)
    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,
            )
Exemple #10
0
 def test_convert_to_exchange_trading_pair(self):
     trading_pair = "BTC-USDT"
     self.assertEqual("BTC/USDT", utils.convert_to_exchange_trading_pair(trading_pair))
    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}")
        ascend_ex_trading_rule = self._ascend_ex_trading_rules[trading_pair]

        amount = self.quantize_order_amount(trading_pair, amount)
        price = self.quantize_order_price(trading_pair, price)

        try:
            # ascend_ex has a unique way of determening if the order has enough "worth" to be posted
            # see https://ascendex.github.io/ascendex-pro-api/#place-order
            notional = Decimal(price * amount)
            if notional < ascend_ex_trading_rule.minNotional or notional > ascend_ex_trading_rule.maxNotional:
                raise ValueError(
                    f"Notional amount {notional} is not withing the range of {ascend_ex_trading_rule.minNotional}-{ascend_ex_trading_rule.maxNotional}."
                )

            # TODO: check balance
            [exchange_order_id, timestamp
             ] = ascend_ex_utils.gen_exchange_order_id(self._account_uid,
                                                       order_id)

            api_params = {
                "id":
                exchange_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":
                trade_type.name
            }

            self.start_tracking_order(order_id, exchange_order_id,
                                      trading_pair, trade_type, price, amount,
                                      order_type)

            await self._api_request("post",
                                    "cash/order",
                                    api_params,
                                    True,
                                    force_auth_path_url="order")
            tracked_order = self._in_flight_orders.get(order_id)

            if tracked_order is not None:
                self.logger().info(
                    f"Created {order_type.name} {trade_type.name} order {order_id} for "
                    f"{amount} {trading_pair}.")

            event_tag = MarketEvent.BuyOrderCreated if trade_type is TradeType.BUY else MarketEvent.SellOrderCreated
            event_class = BuyOrderCreatedEvent if trade_type is TradeType.BUY else SellOrderCreatedEvent
            self.trigger_event(
                event_tag,
                event_class(self.current_timestamp,
                            order_type,
                            trading_pair,
                            amount,
                            price,
                            order_id,
                            exchange_order_id=exchange_order_id))
        except asyncio.CancelledError:
            raise
        except Exception as e:
            self.stop_tracking_order(order_id)
            self.logger().network(
                f"Error submitting {trade_type.name} {order_type.name} order to AscendEx for "
                f"{amount} {trading_pair} "
                f"{price}.",
                exc_info=True,
                app_warning_msg=str(e))
            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)
Exemple #14
0
    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:
            # TODO: check balance
            [exchange_order_id, timestamp
             ] = ascend_ex_utils.gen_exchange_order_id(self._account_uid,
                                                       order_id)

            api_params = {
                "id":
                exchange_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":
                trade_type.name
            }

            self.start_tracking_order(order_id, exchange_order_id,
                                      trading_pair, trade_type, price, amount,
                                      order_type)

            await self._api_request(method="post",
                                    path_url="cash/order",
                                    params=api_params,
                                    is_auth_required=True,
                                    force_auth_path_url="order")
            tracked_order = self._in_flight_orders.get(order_id)

            if tracked_order is not None:
                self.logger().info(
                    f"Created {order_type.name} {trade_type.name} order {order_id} for "
                    f"{amount} {trading_pair}.")

            event_tag = MarketEvent.BuyOrderCreated if trade_type is TradeType.BUY else MarketEvent.SellOrderCreated
            event_class = BuyOrderCreatedEvent if trade_type is TradeType.BUY else SellOrderCreatedEvent
            self.trigger_event(
                event_tag,
                event_class(self.current_timestamp,
                            order_type,
                            trading_pair,
                            amount,
                            price,
                            order_id,
                            exchange_order_id=exchange_order_id))
        except asyncio.CancelledError:
            raise
        except Exception as e:
            self.stop_tracking_order(order_id)
            self.logger().network(
                f"Error submitting {trade_type.name} {order_type.name} order to AscendEx for "
                f"{amount} {trading_pair} "
                f"{price}.",
                exc_info=True,
                app_warning_msg=str(e))
            self.trigger_event(
                MarketEvent.OrderFailure,
                MarketOrderFailureEvent(self.current_timestamp, order_id,
                                        order_type))