Example #1
0
    def _process_order_event_message(self, order_msg: Dict[str, Any]):
        """
        Updates in-flight order and triggers cancellation or failure event if needed.
        :param order_msg: The order event message payload
        """
        client_order_id = str(order_msg["ClientOrderId"])
        if client_order_id in self.in_flight_orders:
            tracked_order = self.in_flight_orders[client_order_id]
            was_locally_working = tracked_order.is_locally_working

            # Update order execution status
            tracked_order.last_state = order_msg["OrderState"]

            if was_locally_working and tracked_order.is_working:
                self.trigger_order_created_event(tracked_order)
            elif tracked_order.is_cancelled:
                self.logger().info(
                    f"Successfully cancelled order {client_order_id}")
                self.trigger_event(
                    MarketEvent.OrderCancelled,
                    OrderCancelledEvent(self.current_timestamp,
                                        client_order_id))
                self.stop_tracking_order(client_order_id)
            elif tracked_order.is_failure:
                self.logger().info(
                    f"The market order {client_order_id} has failed according to order status event. "
                    f"Reason: {order_msg['ChangeReason']}")
                self.trigger_event(
                    MarketEvent.OrderFailure,
                    MarketOrderFailureEvent(self.current_timestamp,
                                            client_order_id,
                                            tracked_order.order_type))
                self.stop_tracking_order(client_order_id)
Example #2
0
    async def _process_stuck_order(self, tracked_order):
        order_id = tracked_order.client_order_id
        open_orders = await self.get_open_orders()
        matched_orders = [
            order for order in open_orders
            if str(order.client_order_id) == str(order_id)
        ]

        if len(matched_orders) == 1:
            tracked_order.update_exchange_order_id(
                str(matched_orders[0].exchange_order_id))
            del self._order_not_created_records[order_id]

            return

        self._order_not_created_records[
            order_id] = self._order_not_created_records.get(order_id, 0) + 1
        if self._order_not_created_records[
                order_id] >= self.ORDER_NOT_CREATED_ID_COUNT:
            self.trigger_event(
                MarketEvent.OrderFailure,
                MarketOrderFailureEvent(self.current_timestamp, order_id,
                                        tracked_order.order_type))
            tracked_order.last_state = "fail"
            self.stop_tracking_order(order_id)
Example #3
0
    def test_remaining_quantity_updated_after_failed_order_event(self):
        self.limit_buy_strategy.logger().setLevel(1)
        self.limit_buy_strategy.logger().addHandler(self)

        self.clock.add_iterator(self.limit_buy_strategy)
        # check no orders are placed before time delay
        self.clock.backtest_til(self.start_timestamp)
        self.assertEqual(0, len(self.limit_buy_strategy.active_bids))

        # one order created after first tick
        self.clock.backtest_til(self.start_timestamp +
                                math.ceil(self.clock_tick_size /
                                          self.order_delay_time))
        self.assertEqual(1, len(self.limit_buy_strategy.active_bids))
        bid_order: LimitOrder = self.limit_buy_strategy.active_bids[0][1]
        self.assertEqual(1, bid_order.quantity)
        self.assertEqual(self.limit_buy_strategy._quantity_remaining, 1)

        # Simulate order cancel
        self.market.trigger_event(
            MarketEvent.OrderFailure,
            MarketOrderFailureEvent(self.market.current_timestamp,
                                    bid_order.client_order_id,
                                    OrderType.LIMIT))

        self.assertEqual(0, len(self.limit_buy_strategy.active_bids))
        self.assertEqual(self.limit_buy_strategy._quantity_remaining, 2)

        self.assertTrue(
            self._is_logged(
                'INFO',
                f"Updating status after order fail (id: {bid_order.client_order_id})"
            ))
    async def execute_sell(
            self,
            order_id: str,
            trading_pair: str,
            amount: Decimal,
            order_type: OrderType,
            price: Optional[Decimal] = Decimal("NaN"),
    ):
        try:
            await self.execute_order(TradeType.SELL, order_id, trading_pair,
                                     amount, order_type, price)
            tracked_order = self.in_flight_orders.get(order_id)
            if tracked_order is not None:
                self.trigger_event(
                    SELL_ORDER_CREATED_EVENT,
                    SellOrderCreatedEvent(
                        now(),
                        order_type,
                        trading_pair,
                        Decimal(amount),
                        Decimal(price),
                        order_id,
                        tracked_order.creation_timestamp,
                    ),
                )

        except ValueError as e:
            # never tracked, so no need to stop tracking
            self.trigger_event(
                ORDER_FAILURE_EVENT,
                MarketOrderFailureEvent(now(), order_id, order_type))
            self.logger().warning(
                f"Failed to place {order_id} on bitmex. {str(e)}")
Example #5
0
    def _process_order_message(self, order_msg: Dict[str, Any]):
        """
        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)
        """
        exchange_order_id = str(order_msg['id'])
        """
        Currently wazirx api are not supporting client_order_id, so looping through
        each in flight order and matching exchange order id.
        """
        for order in list(self._in_flight_orders.values()):
            if str(order.exchange_order_id) == str(exchange_order_id):
                client_order_id = order.client_order_id
                tracked_order = self._in_flight_orders[client_order_id]
                tracked_order.last_state = order_msg["status"]

                if tracked_order.is_cancelled:
                    self.logger().info(
                        f"Successfully cancelled order {client_order_id}.")
                    self.trigger_event(
                        MarketEvent.OrderCancelled,
                        OrderCancelledEvent(self.current_timestamp,
                                            client_order_id))
                    tracked_order.cancelled_event.set()
                    self.stop_tracking_order(client_order_id)
                elif tracked_order.is_failure:
                    self.logger().info(
                        f"The market order {client_order_id} has failed according to order status API. "
                    )
                    self.trigger_event(
                        MarketEvent.OrderFailure,
                        MarketOrderFailureEvent(self.current_timestamp,
                                                client_order_id,
                                                tracked_order.order_type))
                    self.stop_tracking_order(client_order_id)
Example #6
0
 def _process_order_message(self, order_msg: Dict[str, Any]):
     """
     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)
     """
     client_order_id = order_msg["client_oid"]
     if client_order_id not in self._in_flight_orders:
         return
     tracked_order = self._in_flight_orders[client_order_id]
     # Update order execution status
     tracked_order.last_state = order_msg["status"]
     if tracked_order.is_cancelled:
         self.logger().info(
             f"Successfully cancelled order {client_order_id}.")
         self.trigger_event(
             MarketEvent.OrderCancelled,
             OrderCancelledEvent(self.current_timestamp, client_order_id))
         tracked_order.cancelled_event.set()
         self.stop_tracking_order(client_order_id)
     elif tracked_order.is_failure:
         self.logger().info(
             f"The market order {client_order_id} has failed according to order status API. "
             f"Reason: {crypto_com_utils.get_api_reason(order_msg['reason'])}"
         )
         self.trigger_event(
             MarketEvent.OrderFailure,
             MarketOrderFailureEvent(self.current_timestamp,
                                     client_order_id,
                                     tracked_order.order_type))
         self.stop_tracking_order(client_order_id)
    async def _update_order_status(self):
        """
        Calls REST API to get status update for each in-flight order.
        """
        last_tick = int(self._last_poll_timestamp /
                        CONSTANTS.UPDATE_ORDER_STATUS_INTERVAL)
        current_tick = (0 if math.isnan(self.current_timestamp) else int(
            self.current_timestamp / CONSTANTS.UPDATE_ORDER_STATUS_INTERVAL))

        if current_tick > last_tick and len(self._in_flight_orders) > 0:
            tracked_orders = list(self._in_flight_orders.values())
            tasks = []
            for tracked_order in tracked_orders:
                try:
                    exchange_order_id = await tracked_order.get_exchange_order_id(
                    )
                except asyncio.TimeoutError:
                    self.logger().network(
                        f"Skipped order status update for {tracked_order.client_order_id} "
                        "- waiting for exchange order id.")
                    continue
                trading_pair = convert_to_exchange_trading_pair(
                    tracked_order.trading_pair)
                tasks.append(
                    self._api_request(
                        "GET",
                        CONSTANTS.ORDER_STATUS_PATH_URL.format(
                            id=exchange_order_id),
                        params={'currency_pair': trading_pair},
                        is_auth_required=True,
                        limit_id=CONSTANTS.ORDER_STATUS_LIMIT_ID))
            self.logger().debug(
                f"Polling for order status updates of {len(tasks)} orders.")
            responses = await safe_gather(*tasks, return_exceptions=True)
            for response, tracked_order in zip(responses, tracked_orders):
                client_order_id = tracked_order.client_order_id
                if isinstance(response, GateIoAPIError):
                    if response.error_label == 'ORDER_NOT_FOUND':
                        self._order_not_found_records[client_order_id] = \
                            self._order_not_found_records.get(client_order_id, 0) + 1
                        if self._order_not_found_records[
                                client_order_id] < self.ORDER_NOT_EXIST_CONFIRMATION_COUNT:
                            # Wait until the order not found error have repeated a few times before actually treating
                            # it as failed. See: https://github.com/CoinAlpha/hummingbot/issues/601
                            continue
                        self.trigger_event(
                            MarketEvent.OrderFailure,
                            MarketOrderFailureEvent(self.current_timestamp,
                                                    client_order_id,
                                                    tracked_order.order_type))
                        self.stop_tracking_order(client_order_id)
                    else:
                        continue
                elif "id" not in response:
                    self.logger().info(
                        f"_update_order_status id not in resp: {response}")
                    continue
                else:
                    self._process_order_message(response)
 def simulate_order_failed(market_info: MarketTradingPairTuple,
                           order: Union[LimitOrder, MarketOrder]):
     market_info.market.trigger_event(
         MarketEvent.OrderFailure,
         MarketOrderFailureEvent(
             int(time.time() * 1e3), order.client_order_id if isinstance(
                 order, LimitOrder) else order.order_id, OrderType.LIMIT
             if isinstance(order, LimitOrder) else OrderType.MARKET))
Example #9
0
 def _trigger_failure_event(self, order: InFlightOrder):
     self._connector.trigger_event(
         MarketEvent.OrderFailure,
         MarketOrderFailureEvent(
             timestamp=self.current_timestamp,
             order_id=order.client_order_id,
             order_type=order.order_type,
         ),
     )
 async def update_swap_order(self, update_result: Dict[str, any],
                             tracked_order: UniswapInFlightOrder):
     if update_result.get("confirmed", False):
         if update_result["receipt"].get("status", 0) == 1:
             order_id = await tracked_order.get_exchange_order_id()
             gas_used = update_result["receipt"]["gasUsed"]
             gas_price = tracked_order.gas_price
             fee = Decimal(str(gas_used)) * Decimal(
                 str(gas_price)) / Decimal(str(1e9))
             self.trigger_event(
                 MarketEvent.OrderFilled,
                 OrderFilledEvent(self.current_timestamp,
                                  tracked_order.client_order_id,
                                  tracked_order.trading_pair,
                                  tracked_order.trade_type,
                                  tracked_order.order_type,
                                  Decimal(str(tracked_order.price)),
                                  Decimal(str(tracked_order.amount)),
                                  AddedToCostTradeFee(flat_fees=[
                                      TokenAmount(tracked_order.fee_asset,
                                                  Decimal(str(fee)))
                                  ]),
                                  exchange_trade_id=order_id))
             tracked_order.last_state = "FILLED"
             self.logger().info(
                 f"The {tracked_order.trade_type.name} order "
                 f"{tracked_order.client_order_id} has completed "
                 f"according to order status API.")
             event_tag = MarketEvent.BuyOrderCompleted if tracked_order.trade_type is TradeType.BUY \
                 else MarketEvent.SellOrderCompleted
             event_class = BuyOrderCompletedEvent if tracked_order.trade_type is TradeType.BUY \
                 else SellOrderCompletedEvent
             self.trigger_event(
                 event_tag,
                 event_class(self.current_timestamp,
                             tracked_order.client_order_id,
                             tracked_order.base_asset,
                             tracked_order.quote_asset,
                             tracked_order.fee_asset,
                             tracked_order.executed_amount_base,
                             tracked_order.executed_amount_quote,
                             float(fee), tracked_order.order_type))
             self.stop_tracking_order(tracked_order.client_order_id)
         else:
             self.logger().info(
                 f"The {tracked_order.type} order {tracked_order.client_order_id} has failed according to order status API. "
             )
             self.trigger_event(
                 MarketEvent.OrderFailure,
                 MarketOrderFailureEvent(self.current_timestamp,
                                         tracked_order.client_order_id,
                                         tracked_order.order_type))
             self.stop_tracking_order(tracked_order.client_order_id)
Example #11
0
    def _process_order_message(self, order_msg: Dict[str, Any]):
        """
        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)
        Example Order:
        {
            "id": 9401,
            "market": "rogerbtc",
            "kind": "ask",
            "side": "sell",
            "ord_type": "limit",
            "price": "0.00000099",
            "avg_price": "0.00000099",
            "state": "wait",
            "origin_volume": "7000.0",
            "remaining_volume": "2810.1",
            "executed_volume": "4189.9",
            "at": 1596481983,
            "created_at": 1596481983,
            "updated_at": 1596553643,
            "trades_count": 272
        }
        """
        exchange_order_id = str(order_msg["id"])

        tracked_orders = list(self._in_flight_orders.values())
        track_order = [o for o in tracked_orders if exchange_order_id == o.exchange_order_id]
        if not track_order:
            return
        tracked_order = track_order[0]
        # Estimate fee
        order_msg["trade_fee"] = self.estimate_fee_pct(tracked_order.order_type is OrderType.LIMIT_MAKER)
        try:
            updated = tracked_order.update_with_order_update(order_msg)
        except Exception as e:
            self.logger().error(f"Error in order update for {tracked_order.exchange_order_id}. Message: {order_msg}\n{e}")
            traceback.print_exc()
            raise e
        if updated:
            safe_ensure_future(self._trigger_order_fill(tracked_order, order_msg))
        if tracked_order.is_cancelled:
            self.logger().info(f"Successfully cancelled order {tracked_order.client_order_id}.")
            self.stop_tracking_order(tracked_order.client_order_id)
            self.trigger_event(MarketEvent.OrderCancelled,
                               OrderCancelledEvent(self.current_timestamp, tracked_order.client_order_id))
            tracked_order.cancelled_event.set()
        elif tracked_order.is_failure:
            self.logger().info(f"The market order {tracked_order.client_order_id} has failed according to order status API. ")
            self.trigger_event(MarketEvent.OrderFailure,
                               MarketOrderFailureEvent(
                                   self.current_timestamp, tracked_order.client_order_id, tracked_order.order_type))
            tracked_order.last_state = "fail"
            self.stop_tracking_order(tracked_order.client_order_id)
    def _process_order_message(self, order_msg: Dict[str, Any]):
        """
        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)
        Example Order:
        {
            "id": "4345613661",
            "clientOrderId": "57d5525562c945448e3cbd559bd068c3",
            "symbol": "BCCBTC",
            "side": "sell",
            "status": "new",
            "type": "limit",
            "timeInForce": "GTC",
            "quantity": "0.013",
            "price": "0.100000",
            "cumQuantity": "0.000",
            "postOnly": false,
            "createdAt": "2017-10-20T12:17:12.245Z",
            "updatedAt": "2017-10-20T12:17:12.245Z",
            "reportType": "status"
        }
        """
        client_order_id = order_msg["clientOrderId"]
        if client_order_id not in self._in_flight_orders:
            return
        tracked_order = self._in_flight_orders[client_order_id]
        # Update order execution status
        tracked_order.last_state = order_msg["status"]
        # update order
        tracked_order.executed_amount_base = Decimal(order_msg["cumQuantity"])
        tracked_order.executed_amount_quote = Decimal(
            order_msg["price"]) * Decimal(order_msg["cumQuantity"])

        if tracked_order.is_cancelled:
            self.logger().info(
                f"Successfully canceled order {client_order_id}.")
            self.stop_tracking_order(client_order_id)
            self.trigger_event(
                MarketEvent.OrderCancelled,
                OrderCancelledEvent(self.current_timestamp, client_order_id))
            tracked_order.cancelled_event.set()
        elif tracked_order.is_failure:
            self.logger().info(
                f"The market order {client_order_id} has failed according to order status API. "
            )
            self.trigger_event(
                MarketEvent.OrderFailure,
                MarketOrderFailureEvent(self.current_timestamp,
                                        client_order_id,
                                        tracked_order.order_type))
            self.stop_tracking_order(client_order_id)
    async def _update_order_status(self):
        """
        Calls REST API to get status update for each in-flight order.
        """
        last_tick = int(self._last_poll_timestamp /
                        Constants.UPDATE_ORDER_STATUS_INTERVAL)
        current_tick = int(self.current_timestamp /
                           Constants.UPDATE_ORDER_STATUS_INTERVAL)

        if current_tick > last_tick and len(self._in_flight_orders) > 0:
            tracked_orders = list(self._in_flight_orders.values())
            tasks = []
            for tracked_order in tracked_orders:
                # exchange_order_id = await tracked_order.get_exchange_order_id()
                order_id = tracked_order.client_order_id
                tasks.append(
                    self._api_request(
                        "GET",
                        Constants.ENDPOINT["ORDER_STATUS"].format(id=order_id),
                        is_auth_required=True))
            self.logger().debug(
                f"Polling for order status updates of {len(tasks)} orders.")
            responses = await safe_gather(*tasks, return_exceptions=True)
            for response, tracked_order in zip(responses, tracked_orders):
                client_order_id = tracked_order.client_order_id
                if isinstance(response, HitbtcAPIError):
                    err = response.error_payload.get('error',
                                                     response.error_payload)
                    if err.get('code') == 20002:
                        self._order_not_found_records[client_order_id] = \
                            self._order_not_found_records.get(client_order_id, 0) + 1
                        if self._order_not_found_records[
                                client_order_id] < self.ORDER_NOT_EXIST_CONFIRMATION_COUNT:
                            # Wait until the order not found error have repeated a few times before actually treating
                            # it as failed. See: https://github.com/CoinAlpha/hummingbot/issues/601
                            continue
                        self.trigger_event(
                            MarketEvent.OrderFailure,
                            MarketOrderFailureEvent(self.current_timestamp,
                                                    client_order_id,
                                                    tracked_order.order_type))
                        self.stop_tracking_order(client_order_id)
                    else:
                        continue
                elif "clientOrderId" not in response:
                    self.logger().info(
                        f"_update_order_status clientOrderId not in resp: {response}"
                    )
                    continue
                else:
                    self._process_order_message(response)
Example #14
0
 def stop_tracking_order_exceed_not_found_limit(self, tracked_order: AltmarketsInFlightOrder):
     """
     Increments and checks if the tracked order has exceed the ORDER_NOT_EXIST_CONFIRMATION_COUNT limit.
     If true, Triggers a MarketOrderFailureEvent and stops tracking the order.
     """
     client_order_id = tracked_order.client_order_id
     self._order_not_found_records[client_order_id] = self._order_not_found_records.get(client_order_id, 0) + 1
     if self._order_not_found_records[client_order_id] >= self.ORDER_NOT_EXIST_CONFIRMATION_COUNT:
         # Wait until the order not found error have repeated a few times before actually treating
         # it as failed. See: https://github.com/CoinAlpha/hummingbot/issues/601
         self.trigger_event(MarketEvent.OrderFailure,
                            MarketOrderFailureEvent(
                                self.current_timestamp, client_order_id, tracked_order.order_type))
         tracked_order.last_state = "fail"
         self.stop_tracking_order(client_order_id)
    async def _update_order_status(self):
        """
        Calls REST API to get status update for each in-flight order.
        """
        last_tick = int(self._last_poll_timestamp /
                        Constants.UPDATE_ORDER_STATUS_INTERVAL)
        current_tick = int(self.current_timestamp /
                           Constants.UPDATE_ORDER_STATUS_INTERVAL)

        if current_tick > last_tick and len(self._in_flight_orders) > 0:
            tracked_orders = list(self._in_flight_orders.values())
            api_params = {
                'symbol': None,
                'orderSide': None,
                'orderStatuses': ["NEW", "PARTIALLY_FILLED"],
                'size': 500,
                'bookmarkOrderId': None
            }
            self.logger().debug(
                f"Polling for order status updates of {len(tracked_orders)} orders."
            )
            open_orders = await self._api_request(
                "POST",
                Constants.ENDPOINT["ORDER_STATUS"],
                api_params,
                is_auth_required=True)

            open_orders_dict = {o['id']: o for o in open_orders}
            found_ex_order_ids = list(open_orders_dict.keys())

            for tracked_order in tracked_orders:
                client_order_id = tracked_order.client_order_id
                ex_order_id = tracked_order.exchange_order_id
                if ex_order_id not in found_ex_order_ids:
                    self._order_not_found_records[client_order_id] = \
                        self._order_not_found_records.get(client_order_id, 0) + 1
                    if self._order_not_found_records[
                            client_order_id] < self.ORDER_NOT_EXIST_CONFIRMATION_COUNT:
                        # Wait until the order is not found a few times before actually treating it as failed.
                        continue
                    self.trigger_event(
                        MarketEvent.OrderFailure,
                        MarketOrderFailureEvent(self.current_timestamp,
                                                client_order_id,
                                                tracked_order.order_type))
                    self.stop_tracking_order(client_order_id)
                else:
                    self._process_order_message(open_orders_dict[ex_order_id])
Example #16
0
    async def _process_order_message(self, order_msg: Dict[str, Any]):
        """
        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)
        """
        for order in self._in_flight_orders.values():
            await order.get_exchange_order_id()
        exchange_order_id = str(order_msg["order_id"])
        tracked_orders = list(self._in_flight_orders.values())
        tracked_order = [
            order for order in tracked_orders
            if exchange_order_id == order.exchange_order_id
        ]
        if not tracked_order:
            return
        tracked_order = tracked_order[0]
        client_order_id = tracked_order.client_order_id

        # Update order execution status
        if "status" in order_msg:  # REST API
            tracked_order.last_state = CONSTANTS.ORDER_STATUS[int(
                order_msg["status"])]
        elif "state" in order_msg:  # WebSocket
            tracked_order.last_state = CONSTANTS.ORDER_STATUS[int(
                order_msg["state"])]

        if tracked_order.is_cancelled:
            self.logger().info(
                f"Successfully cancelled order {client_order_id}.")
            self.trigger_event(
                MarketEvent.OrderCancelled,
                OrderCancelledEvent(self.current_timestamp, client_order_id))
            tracked_order.cancelled_event.set()
            self.stop_tracking_order(client_order_id)
        elif tracked_order.is_failure:
            self.logger().info(
                f"The market order {client_order_id} has failed according to order status API. "
            )
            self.trigger_event(
                MarketEvent.OrderFailure,
                MarketOrderFailureEvent(self.current_timestamp,
                                        client_order_id,
                                        tracked_order.order_type))
            self.stop_tracking_order(client_order_id)
Example #17
0
    def _process_order_message(self, order_msg: Dict[str, Any]):
        """
        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)
        """
        ex_order_id = order_msg["orderid"]

        client_order_id = None
        for tracked_order in self.in_flight_orders.values():
            if tracked_order.exchange_order_id == ex_order_id:
                client_order_id = tracked_order.client_order_id
                break

        if client_order_id not in self.in_flight_orders:
            return
        tracked_order: K2InFlightOrder = self.in_flight_orders[client_order_id]
        # Update order execution status
        tracked_order.last_state = constants.ORDER_STATUS[order_msg["status"]]

        if tracked_order.is_cancelled:
            self.logger().info(
                f"Successfully cancelled order {client_order_id}.")
            self.trigger_event(
                MarketEvent.OrderCancelled,
                OrderCancelledEvent(self.current_timestamp, client_order_id))
            tracked_order.cancelled_event.set()
            self.stop_tracking_order(client_order_id)
        elif tracked_order.is_failure:
            self.logger().info(
                f"The order {client_order_id} has failed according to order status API. "
            )
            self.trigger_event(
                MarketEvent.OrderFailure,
                MarketOrderFailureEvent(self.current_timestamp,
                                        client_order_id,
                                        tracked_order.order_type))
            self.stop_tracking_order(client_order_id)
    async def _create_order(self, trade_type: TradeType, order_id: str,
                            trading_pair: str, amount: Decimal,
                            price: Decimal):
        """
        Calls buy or sell API end point to place an order, starts tracking the order and triggers relevant order events.
        :param trade_type: BUY or SELL
        :param order_id: Internal order id (also called client_order_id)
        :param trading_pair: The market to place order
        :param amount: The order amount (in base token value)
        :param price: The order price
        """

        amount = self.quantize_order_amount(trading_pair, amount)
        price = self.quantize_order_price(trading_pair, price)
        base, quote = trading_pair.split("-")
        gas_price = get_gas_price()
        api_params = {
            "base": self._token_addresses[base],
            "quote": self._token_addresses[quote],
            "amount": str(amount),
            "maxPrice": str(price),
            "maxSwaps": str(self._max_swaps),
            "gasPrice": str(gas_price),
        }
        self.start_tracking_order(order_id, None, trading_pair, trade_type,
                                  price, amount, gas_price)
        try:
            order_result = await self._api_request(
                "post", f"balancer/{trade_type.name.lower()}", api_params)
            hash = order_result.get("txHash")
            tracked_order = self._in_flight_orders.get(order_id)
            if tracked_order is not None:
                self.logger().info(
                    f"Created {trade_type.name} order {order_id} txHash: {hash} "
                    f"for {amount} {trading_pair}.")
                tracked_order.update_exchange_order_id(hash)
                tracked_order.gas_price = gas_price
            if hash is not None:
                tracked_order.fee_asset = "ETH"
                tracked_order.executed_amount_base = amount
                tracked_order.executed_amount_quote = amount * price
                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, OrderType.LIMIT,
                                trading_pair, amount, price, order_id, hash))
            else:
                self.trigger_event(
                    MarketEvent.OrderFailure,
                    MarketOrderFailureEvent(self.current_timestamp, order_id,
                                            OrderType.LIMIT))
        except asyncio.CancelledError:
            raise
        except Exception as e:
            self.stop_tracking_order(order_id)
            self.logger().network(
                f"Error submitting {trade_type.name} order to Balancer 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,
                                        OrderType.LIMIT))
Example #19
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 (also called 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}")
        trading_rule = self._trading_rules[trading_pair]

        amount = self.quantize_order_amount(trading_pair, amount)
        price = self.quantize_order_price(trading_pair, price)
        if amount < trading_rule.min_order_size:
            raise ValueError(
                f"Buy order amount {amount} is lower than the minimum order size "
                f"{trading_rule.min_order_size}.")
        api_params = {
            "instrument_name":
            crypto_com_utils.convert_to_exchange_trading_pair(trading_pair),
            "side":
            trade_type.name,
            "type":
            "LIMIT",
            "price":
            f"{price:f}",
            "quantity":
            f"{amount:f}",
            "client_oid":
            order_id
        }
        if order_type is OrderType.LIMIT_MAKER:
            api_params["exec_inst"] = "POST_ONLY"
        self.start_tracking_order(order_id, None, trading_pair, trade_type,
                                  price, amount, order_type)
        try:
            order_result = await self._api_request("post",
                                                   "private/create-order",
                                                   api_params, True)
            exchange_order_id = str(order_result["result"]["order_id"])
            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}.")
                tracked_order.update_exchange_order_id(exchange_order_id)

            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))
        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 Crypto.com 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))
Example #20
0
    async def _create_order(self,
                            trade_type: TradeType,
                            order_id: str,
                            trading_pair: str,
                            amount: Decimal,
                            price: Decimal = s_decimal_0,
                            order_type: OrderType = OrderType.MARKET):
        """
        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 (also called client_order_id)
        :param trading_pair: The market to place order
        :param amount: The order amount (in base token value)
        :param price: The order price
        :param order_type: The order type
        """
        trading_rule: TradingRule = self._trading_rules[trading_pair]

        trading_pair_ids: Dict[
            str,
            int] = await self._order_book_tracker.data_source.get_instrument_ids(
            )

        try:
            amount: Decimal = self.quantize_order_amount(trading_pair, amount)
            if amount < trading_rule.min_order_size:
                raise ValueError(
                    f"{trade_type.name} order amount {amount} is lower than the minimum order size "
                    f"{trading_rule.min_order_size}.")

            params = {
                "InstrumentId": trading_pair_ids[trading_pair],
                "OMSId": 1,
                "AccountId": await self.initialized_account_id(),
                "ClientOrderId": int(order_id),
                "Side": 0 if trade_type == TradeType.BUY else 1,
                "Quantity": amount,
                "TimeInForce": 1,  # GTC
            }

            if order_type.is_limit_type():
                price: Decimal = self.quantize_order_price(trading_pair, price)

                params.update({
                    "OrderType": 2,  # Limit
                    "LimitPrice": price,
                })
            else:
                params.update({
                    "OrderType": 1  # Market
                })

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

            send_order_results = await self._api_request(
                method="POST",
                path_url=CONSTANTS.SEND_ORDER_PATH_URL,
                data=params,
                is_auth_required=True)

            if send_order_results["status"] == "Rejected":
                raise ValueError(
                    f"Order is rejected by the API. "
                    f"Parameters: {params} Error Msg: {send_order_results['errormsg']}"
                )

            exchange_order_id = str(send_order_results["OrderId"])
            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}.")
                tracked_order.update_exchange_order_id(exchange_order_id)

        except asyncio.CancelledError:
            raise
        except Exception as e:
            self.stop_tracking_order(order_id)
            self.trigger_event(
                MarketEvent.OrderFailure,
                MarketOrderFailureEvent(self.current_timestamp, order_id,
                                        order_type))
            self.logger().network(
                f"Error submitting {trade_type.name} {order_type.name} order to NDAX for "
                f"{amount} {trading_pair} {price}. Error: {str(e)}",
                exc_info=True,
                app_warning_msg="Error submitting order to NDAX. ")
    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))
Example #22
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 (also called 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
        """
        trading_rule = self._trading_rules[trading_pair]

        try:
            amount = self.quantize_order_amount(trading_pair, amount)
            price = self.quantize_order_price(
                trading_pair, s_decimal_0 if math.isnan(price) else price)
            if amount < trading_rule.min_order_size:
                raise ValueError(
                    f"Buy order amount {amount} is lower than the minimum order size "
                    f"{trading_rule.min_order_size}.")
            order_type_str = order_type.name.lower().split("_")[0]
            api_params = {
                "market": convert_to_exchange_trading_pair(trading_pair),
                "side": trade_type.name.lower(),
                "ord_type": order_type_str,
                # "price": f"{price:f}",
                "client_id": order_id,
                "volume": f"{amount:f}",
            }
            if order_type is not OrderType.MARKET:
                api_params['price'] = f"{price:f}"
            # if order_type is OrderType.LIMIT_MAKER:
            #     api_params["postOnly"] = "true"
            self.start_tracking_order(order_id, None, trading_pair, trade_type,
                                      price, amount, order_type)

            order_result = await self._api_request(
                "POST",
                Constants.ENDPOINT["ORDER_CREATE"],
                params=api_params,
                is_auth_required=True,
                limit_id=Constants.RL_ID_ORDER_CREATE,
                disable_retries=True)
            exchange_order_id = str(order_result["id"])
            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}.")
                tracked_order.update_exchange_order_id(exchange_order_id)
            else:
                raise Exception('Order not tracked.')
            if trade_type is TradeType.BUY:
                event_tag = MarketEvent.BuyOrderCreated
                event_cls = BuyOrderCreatedEvent
            else:
                event_tag = MarketEvent.SellOrderCreated
                event_cls = SellOrderCreatedEvent
            self.trigger_event(
                event_tag,
                event_cls(self.current_timestamp, order_type, trading_pair,
                          amount, price, order_id, exchange_order_id))
        except asyncio.CancelledError:
            raise
        except Exception as e:
            if isinstance(e, AltmarketsAPIError):
                error_reason = e.error_payload.get('error', {}).get(
                    'message', e.error_payload.get('errors'))
            else:
                error_reason = e
            if error_reason and "upstream connect error" not in str(
                    error_reason):
                self.stop_tracking_order(order_id)
                self.trigger_event(
                    MarketEvent.OrderFailure,
                    MarketOrderFailureEvent(self.current_timestamp, order_id,
                                            order_type))
            else:
                self._order_not_created_records[order_id] = 0
            self.logger().network(
                f"Error submitting {trade_type.name} {order_type.name} order to {Constants.EXCHANGE_NAME} for "
                f"{amount} {trading_pair} {price} - {error_reason}.",
                exc_info=True,
                app_warning_msg=
                (f"Error submitting order to {Constants.EXCHANGE_NAME} - {error_reason}."
                 ))
Example #23
0
    async def execute_sell(self,
                           order_id: str,
                           trading_pair: str,
                           amount: Decimal,
                           order_type: OrderType,
                           price: Optional[Decimal] = s_decimal_0):

        trading_rule = self._trading_rules[trading_pair]

        if not order_type.is_limit_type():
            self.trigger_event(
                self.MARKET_ORDER_FAILURE_EVENT_TAG,
                MarketOrderFailureEvent(self.current_timestamp, order_id,
                                        order_type))
            raise Exception(f"Unsupported order type: {order_type}")

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

        if decimal_price * decimal_amount < trading_rule.min_notional_size:
            self.trigger_event(
                self.MARKET_ORDER_FAILURE_EVENT_TAG,
                MarketOrderFailureEvent(self.current_timestamp, order_id,
                                        order_type))
            raise ValueError(
                f"Sell order amount {decimal_amount} is lower than the notional size "
            )

        try:
            exchange_order_id = await self.place_order(order_id, trading_pair,
                                                       decimal_amount, False,
                                                       order_type,
                                                       decimal_price)
            self.start_tracking_order(order_id=order_id,
                                      exchange_order_id=exchange_order_id,
                                      trading_pair=trading_pair,
                                      order_type=order_type,
                                      trade_type=TradeType.SELL,
                                      price=decimal_price,
                                      amount=decimal_amount)
            tracked_order = self._in_flight_orders.get(order_id)
            if tracked_order is not None:
                self.logger().info(
                    f"Created {order_type.name.upper()} sell order {order_id} for {decimal_amount} {trading_pair}."
                )
            self.trigger_event(
                self.MARKET_SELL_ORDER_CREATED_EVENT_TAG,
                SellOrderCreatedEvent(self.current_timestamp, order_type,
                                      trading_pair, decimal_amount,
                                      decimal_price, order_id))
        except asyncio.CancelledError:
            raise
        except Exception as ex:
            self.stop_tracking_order(order_id)
            order_type_str = order_type.name.lower()
            self.logger().network(
                f"Error submitting sell {order_type_str} order to Mexc for "
                f"{decimal_amount} {trading_pair} "
                f"{decimal_price if order_type is OrderType.LIMIT else ''}."
                f"{decimal_price}." + ",ex:" + repr(ex),
                exc_info=True,
                app_warning_msg=
                "Failed to submit sell order to Mexc. Check API key and network connection."
            )
            self.trigger_event(
                self.MARKET_ORDER_FAILURE_EVENT_TAG,
                MarketOrderFailureEvent(self.current_timestamp, order_id,
                                        order_type))
Example #24
0
    async def _update_order_status(self):
        last_tick = int(self._last_poll_timestamp /
                        self.UPDATE_ORDERS_INTERVAL)
        current_tick = int(self.current_timestamp /
                           self.UPDATE_ORDERS_INTERVAL)
        if current_tick > last_tick and len(self._in_flight_orders) > 0:
            tracked_orders = list(self._in_flight_orders.values())
            for tracked_order in tracked_orders:
                try:
                    exchange_order_id = await tracked_order.get_exchange_order_id(
                    )
                    try:
                        order_update = await self.get_order_status(
                            exchange_order_id, tracked_order.trading_pair)
                    except MexcAPIError as ex:
                        err_code = ex.error_payload.get("error").get(
                            'err-code')
                        self.stop_tracking_order(tracked_order.client_order_id)
                        self.logger().info(
                            f"The limit order {tracked_order.client_order_id} "
                            f"has failed according to order status API. - {err_code}"
                        )
                        self.trigger_event(
                            self.MARKET_ORDER_FAILURE_EVENT_TAG,
                            MarketOrderFailureEvent(
                                self.current_timestamp,
                                tracked_order.client_order_id,
                                tracked_order.order_type))
                        continue

                    if order_update is None:
                        self.logger().network(
                            f"Error fetching status update for the order {tracked_order.client_order_id}: "
                            f"{exchange_order_id}.",
                            app_warning_msg=
                            f"Could not fetch updates for the order {tracked_order.client_order_id}. "
                            f"The order has either been filled or canceled.")
                        continue
                    tracked_order.last_state = order_update['state']
                    order_status = order_update['state']
                    new_confirmed_amount = Decimal(
                        order_update['deal_quantity'])
                    execute_amount_diff = new_confirmed_amount - tracked_order.executed_amount_base

                    if execute_amount_diff > s_decimal_0:
                        execute_price = Decimal(
                            Decimal(order_update['deal_amount']) /
                            Decimal(order_update['deal_quantity']))
                        tracked_order.executed_amount_base = Decimal(
                            order_update['deal_quantity'])
                        tracked_order.executed_amount_quote = Decimal(
                            order_update['deal_amount'])

                        order_filled_event = OrderFilledEvent(
                            self.current_timestamp,
                            tracked_order.client_order_id,
                            tracked_order.trading_pair,
                            tracked_order.trade_type,
                            tracked_order.order_type,
                            execute_price,
                            execute_amount_diff,
                            self.get_fee(
                                tracked_order.base_asset,
                                tracked_order.quote_asset,
                                tracked_order.order_type,
                                tracked_order.trade_type,
                                execute_amount_diff,
                                execute_price,
                            ),
                            exchange_trade_id=exchange_order_id)
                        self.logger().info(
                            f"Filled {execute_amount_diff} out of {tracked_order.amount} of the "
                            f"order {tracked_order.client_order_id}.")
                        self.trigger_event(self.MARKET_ORDER_FILLED_EVENT_TAG,
                                           order_filled_event)
                    if order_status == "FILLED":
                        fee_paid, fee_currency = await self.get_deal_detail_fee(
                            tracked_order.exchange_order_id)
                        tracked_order.fee_paid = fee_paid
                        tracked_order.fee_asset = fee_currency
                        tracked_order.last_state = order_status
                        self.stop_tracking_order(tracked_order.client_order_id)
                        if tracked_order.trade_type is TradeType.BUY:
                            self.logger().info(
                                f"The BUY {tracked_order.order_type} order {tracked_order.client_order_id} has completed "
                                f"according to order delta restful API.")
                            self.trigger_event(
                                self.MARKET_BUY_ORDER_COMPLETED_EVENT_TAG,
                                BuyOrderCompletedEvent(
                                    self.current_timestamp,
                                    tracked_order.client_order_id,
                                    tracked_order.base_asset,
                                    tracked_order.quote_asset,
                                    tracked_order.fee_asset
                                    or tracked_order.quote_asset,
                                    tracked_order.executed_amount_base,
                                    tracked_order.executed_amount_quote,
                                    tracked_order.fee_paid,
                                    tracked_order.order_type))
                        elif tracked_order.trade_type is TradeType.SELL:
                            self.logger().info(
                                f"The SELL {tracked_order.order_type} order {tracked_order.client_order_id} has completed "
                                f"according to order delta restful API.")
                            self.trigger_event(
                                self.MARKET_SELL_ORDER_COMPLETED_EVENT_TAG,
                                SellOrderCompletedEvent(
                                    self.current_timestamp,
                                    tracked_order.client_order_id,
                                    tracked_order.base_asset,
                                    tracked_order.quote_asset,
                                    tracked_order.fee_asset
                                    or tracked_order.quote_asset,
                                    tracked_order.executed_amount_base,
                                    tracked_order.executed_amount_quote,
                                    tracked_order.fee_paid,
                                    tracked_order.order_type))
                        continue
                    if order_status == "CANCELED" or order_status == "PARTIALLY_CANCELED":
                        tracked_order.last_state = order_status
                        self.stop_tracking_order(tracked_order.client_order_id)
                        self.logger().info(
                            f"Order {tracked_order.client_order_id} has been cancelled "
                            f"according to order delta restful API.")
                        self.trigger_event(
                            self.MARKET_ORDER_CANCELLED_EVENT_TAG,
                            OrderCancelledEvent(self.current_timestamp,
                                                tracked_order.client_order_id))
                except Exception as ex:
                    self.logger().error("_update_order_status error ..." +
                                        repr(ex),
                                        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 (also called 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}")
        trading_rule = self._trading_rules[trading_pair]

        amount = self.quantize_order_amount(trading_pair, amount)
        price = self.quantize_order_price(trading_pair, price)
        if amount < trading_rule.min_order_size:
            raise ValueError(f"Buy order amount {amount} is lower than the minimum order size "
                             f"{trading_rule.min_order_size}.")
        order_type_str = order_type.name.lower().split("_")[0]
        api_params = {"symbol": convert_to_exchange_trading_pair(trading_pair),
                      "side": trade_type.name.lower(),
                      "type": order_type_str,
                      "price": f"{price:f}",
                      "quantity": f"{amount:f}",
                      "clientOrderId": order_id,
                      # Without strict validate, HitBTC might adjust order prices/sizes.
                      "strictValidate": "true",
                      }
        if order_type is OrderType.LIMIT_MAKER:
            api_params["postOnly"] = "true"
        self.start_tracking_order(order_id, None, trading_pair, trade_type, price, amount, order_type)
        try:
            order_result = await self._api_request("POST", Constants.ENDPOINT["ORDER_CREATE"], api_params, True)
            exchange_order_id = str(order_result["id"])
            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}.")
                tracked_order.update_exchange_order_id(exchange_order_id)
            if trade_type is TradeType.BUY:
                event_tag = MarketEvent.BuyOrderCreated
                event_cls = BuyOrderCreatedEvent
            else:
                event_tag = MarketEvent.SellOrderCreated
                event_cls = SellOrderCreatedEvent
            self.trigger_event(event_tag,
                               event_cls(self.current_timestamp, order_type, trading_pair, amount, price, order_id))
        except asyncio.CancelledError:
            raise
        except HitbtcAPIError as e:
            error_reason = e.error_payload.get('error', {}).get('message')
            self.stop_tracking_order(order_id)
            self.logger().network(
                f"Error submitting {trade_type.name} {order_type.name} order to {Constants.EXCHANGE_NAME} for "
                f"{amount} {trading_pair} {price} - {error_reason}.",
                exc_info=True,
                app_warning_msg=(f"Error submitting order to {Constants.EXCHANGE_NAME} - {error_reason}.")
            )
            self.trigger_event(MarketEvent.OrderFailure,
                               MarketOrderFailureEvent(self.current_timestamp, order_id, order_type))
    async def _update_order_status(self):
        """
        Calls REST API to get status update for each in-flight order.
        """
        if len(self._in_flight_orders) > 0:
            tracked_orders = list(self._in_flight_orders.values())

            tasks = []
            for tracked_order in tracked_orders:
                order_id = await tracked_order.get_exchange_order_id()
                tasks.append(
                    self._api_request("post", "eth/get-receipt",
                                      {"txHash": order_id}))
            update_results = await safe_gather(*tasks, return_exceptions=True)
            for update_result in update_results:
                self.logger().info(
                    f"Polling for order status updates of {len(tasks)} orders."
                )
                if isinstance(update_result, Exception):
                    raise update_result
                if "txHash" not in update_result:
                    self.logger().info(
                        f"_update_order_status txHash not in resp: {update_result}"
                    )
                    continue
                if update_result["confirmed"] is True:
                    if update_result["receipt"]["status"] == 1:
                        gas_used = update_result["receipt"]["gasUsed"]
                        gas_price = tracked_order.gas_price
                        fee = Decimal(str(gas_used)) * Decimal(
                            str(gas_price)) / Decimal(str(1e9))
                        self.trigger_event(
                            MarketEvent.OrderFilled,
                            OrderFilledEvent(
                                self.current_timestamp,
                                tracked_order.client_order_id,
                                tracked_order.trading_pair,
                                tracked_order.trade_type,
                                tracked_order.order_type,
                                Decimal(str(tracked_order.price)),
                                Decimal(str(tracked_order.amount)),
                                TradeFee(0.0, [(tracked_order.fee_asset,
                                                Decimal(str(fee)))]),
                                exchange_trade_id=order_id))
                        tracked_order.last_state = "FILLED"
                        self.logger().info(
                            f"The {tracked_order.trade_type.name} order "
                            f"{tracked_order.client_order_id} has completed "
                            f"according to order status API.")
                        event_tag = MarketEvent.BuyOrderCompleted if tracked_order.trade_type is TradeType.BUY \
                            else MarketEvent.SellOrderCompleted
                        event_class = BuyOrderCompletedEvent if tracked_order.trade_type is TradeType.BUY \
                            else SellOrderCompletedEvent
                        self.trigger_event(
                            event_tag,
                            event_class(self.current_timestamp,
                                        tracked_order.client_order_id,
                                        tracked_order.base_asset,
                                        tracked_order.quote_asset,
                                        tracked_order.fee_asset,
                                        tracked_order.executed_amount_base,
                                        tracked_order.executed_amount_quote,
                                        float(fee), tracked_order.order_type))
                        self.stop_tracking_order(tracked_order.client_order_id)
                    else:
                        self.logger().info(
                            f"The market order {tracked_order.client_order_id} has failed according to order status API. "
                        )
                        self.trigger_event(
                            MarketEvent.OrderFailure,
                            MarketOrderFailureEvent(
                                self.current_timestamp,
                                tracked_order.client_order_id,
                                tracked_order.order_type))
                        self.stop_tracking_order(tracked_order.client_order_id)
Example #27
0
    async def _update_order_status(self):
        last_tick = self._last_poll_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL
        current_tick = self.current_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL
        if current_tick > last_tick and len(self._in_flight_orders) > 0:
            tracked_orders = list(self._in_flight_orders.values())
            tasks = [self.request(path="/fapi/v1/order",
                                  params={
                                      "symbol": convert_to_exchange_trading_pair(order.trading_pair),
                                      "origClientOrderId": order.client_order_id
                                  },
                                  method=MethodType.GET,
                                  add_timestamp=True,
                                  is_signed=True,
                                  return_err=True)
                     for order in tracked_orders]
            self.logger().debug(f"Polling for order status updates of {len(tasks)} orders.")
            results = await safe_gather(*tasks, return_exceptions=True)
            for order_update, tracked_order in zip(results, tracked_orders):
                client_order_id = tracked_order.client_order_id
                if client_order_id not in self._in_flight_orders:
                    continue
                if isinstance(order_update, Exception):
                    # NO_SUCH_ORDER code
                    if order_update["code"] == -2013 or order_update["msg"] == "Order does not exist.":
                        self._order_not_found_records[client_order_id] = \
                            self._order_not_found_records.get(client_order_id, 0) + 1
                        if self._order_not_found_records[client_order_id] < self.ORDER_NOT_EXIST_CONFIRMATION_COUNT:
                            continue
                        self.trigger_event(
                            self.MARKET_ORDER_FAILURE_EVENT_TAG,
                            MarketOrderFailureEvent(self.current_timestamp, client_order_id, tracked_order.order_type)
                        )
                        self.stop_tracking_order(client_order_id)
                    else:
                        self.logger().network(f"Error fetching status update for the order {client_order_id}: "
                                              f"{order_update}.")
                    continue

                tracked_order.last_state = order_update.get("status")
                order_type = OrderType.LIMIT if order_update.get("type") == "LIMIT" else OrderType.MARKET
                executed_amount_base = Decimal(order_update.get("executedQty", "0"))
                executed_amount_quote = Decimal(order_update.get("cumQuote", "0"))

                if tracked_order.is_done:
                    if not tracked_order.is_failure:
                        event_tag = None
                        event_class = None
                        if tracked_order.trade_type is TradeType.BUY:
                            event_tag = self.MARKET_BUY_ORDER_COMPLETED_EVENT_TAG
                            event_class = BuyOrderCompletedEvent
                        else:

                            event_tag = self.MARKET_SELL_ORDER_COMPLETED_EVENT_TAG
                            event_class = SellOrderCompletedEvent
                        self.logger().info(f"The {order_type.name.lower()} {tracked_order.trade_type.name.lower()} order {client_order_id} has "
                                           f"completed according to order status API.")
                        self.trigger_event(event_tag,
                                           event_class(self.current_timestamp,
                                                       client_order_id,
                                                       tracked_order.base_asset,
                                                       tracked_order.quote_asset,
                                                       (tracked_order.fee_asset or tracked_order.base_asset),
                                                       executed_amount_base,
                                                       executed_amount_quote,
                                                       tracked_order.fee_paid,
                                                       order_type))
                    else:
                        if tracked_order.is_cancelled:
                            self.logger().info(f"Successfully cancelled order {client_order_id} according to order status API.")
                            self.trigger_event(self.MARKET_ORDER_CANCELLED_EVENT_TAG,
                                               OrderCancelledEvent(self.current_timestamp,
                                                                   client_order_id))
                        else:
                            self.logger().info(f"The {order_type.name.lower()} order {client_order_id} has failed according to "
                                               f"order status API.")
                            self.trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG,
                                               MarketOrderFailureEvent(self.current_timestamp,
                                                                       client_order_id,
                                                                       order_type))
                    self.stop_tracking_order(client_order_id)
Example #28
0
    async def _user_stream_event_listener(self):
        async for event_message in self._iter_user_event_queue():
            try:
                event_type = event_message.get("e")
                if event_type == "ORDER_TRADE_UPDATE":
                    order_message = event_message.get("o")
                    client_order_id = order_message.get("c")

                    # If the order has already been cancelled
                    if client_order_id not in self._in_flight_orders:
                        continue

                    tracked_order = self._in_flight_orders.get(client_order_id)
                    tracked_order.update_with_execution_report(event_message)

                    # Execution Type: Trade => Filled
                    trade_type = TradeType.BUY if order_message.get("S") == "BUY" else TradeType.SELL
                    if order_message.get("X") in ["PARTIALLY_FILLED", "FILLED"]:
                        order_filled_event = OrderFilledEvent(
                            timestamp=event_message.get("E") * 1e-3,
                            order_id=client_order_id,
                            trading_pair=convert_from_exchange_trading_pair(order_message.get("s")),
                            trade_type=trade_type,
                            order_type=OrderType.LIMIT if order_message.get("o") == "LIMIT" else OrderType.MARKET,
                            price=Decimal(order_message.get("L")),
                            amount=Decimal(order_message.get("l")),
                            trade_fee=self.get_fee(
                                base_currency=tracked_order.base_asset,
                                quote_currency=tracked_order.quote_asset,
                                order_type=tracked_order.order_type,
                                order_side=trade_type,
                                amount=Decimal(order_message.get("q")),
                                price=Decimal(order_message.get("p"))
                            ),
                            exchange_trade_id=order_message.get("t")
                        )
                        self.trigger_event(self.MARKET_ORDER_FILLED_EVENT_TAG, order_filled_event)

                    if tracked_order.is_done:
                        if not tracked_order.is_failure:
                            event_tag = None
                            event_class = None
                            if trade_type is TradeType.BUY:
                                event_tag = self.MARKET_BUY_ORDER_COMPLETED_EVENT_TAG
                                event_class = BuyOrderCompletedEvent
                            else:
                                event_tag = self.MARKET_SELL_ORDER_COMPLETED_EVENT_TAG
                                event_class = SellOrderCompletedEvent
                            self.logger().info(f"The {tracked_order.order_type.name.lower()} {trade_type} order {client_order_id} has completed "
                                               f"according to websocket delta.")
                            self.trigger_event(event_tag,
                                               event_class(self.current_timestamp,
                                                           client_order_id,
                                                           tracked_order.base_asset,
                                                           tracked_order.quote_asset,
                                                           (tracked_order.fee_asset or tracked_order.quote_asset),
                                                           tracked_order.executed_amount_base,
                                                           tracked_order.executed_amount_quote,
                                                           tracked_order.fee_paid,
                                                           tracked_order.order_type))
                        else:
                            if tracked_order.is_cancelled:
                                if tracked_order.client_order_id in self._in_flight_orders:
                                    self.logger().info(f"Successfully cancelled order {tracked_order.client_order_id} according to websocket delta.")
                                    self.trigger_event(self.MARKET_ORDER_CANCELLED_EVENT_TAG,
                                                       OrderCancelledEvent(self.current_timestamp,
                                                                           tracked_order.client_order_id))
                                else:
                                    self.logger().info(f"The {tracked_order.order_type.name.lower()} order {tracked_order.client_order_id} has failed "
                                                       f"according to websocket delta.")
                                    self.trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG,
                                                       MarketOrderFailureEvent(self.current_timestamp,
                                                                               tracked_order.client_order_id,
                                                                               tracked_order.order_type))
                        self.stop_tracking_order(tracked_order.client_order_id)
                elif event_type == "ACCOUNT_UPDATE":
                    update_data = event_message.get("a", {})
                    # update balances
                    for asset in update_data.get("B", []):
                        asset_name = asset["a"]
                        self._account_balances[asset_name] = Decimal(asset["wb"])
                        self._account_available_balances[asset_name] = Decimal(asset["cw"])

                    # update position
                    for asset in update_data.get("P", []):
                        position = self._account_positions.get(f"{asset['s']}{asset['ps']}", None)
                        if position is not None:
                            position.update_position(position_side=PositionSide[asset["ps"]],
                                                     unrealized_pnl = Decimal(asset["up"]),
                                                     entry_price = Decimal(asset["ep"]),
                                                     amount = Decimal(asset["pa"]))
                        else:
                            await self._update_positions()
                elif event_type == "MARGIN_CALL":
                    positions = event_message.get("p", [])
                    total_maint_margin_required = 0
                    # total_pnl = 0
                    negative_pnls_msg = ""
                    for position in positions:
                        existing_position = self._account_positions.get(f"{asset['s']}{asset['ps']}", None)
                        if existing_position is not None:
                            existing_position.update_position(position_side=PositionSide[asset["ps"]],
                                                              unrealized_pnl = Decimal(asset["up"]),
                                                              amount = Decimal(asset["pa"]))
                        total_maint_margin_required += position.get("mm", 0)
                        if position.get("up", 0) < 1:
                            negative_pnls_msg += f"{position.get('s')}: {position.get('up')}, "
                    self.logger().warning("Margin Call: Your position risk is too high, and you are at risk of "
                                          "liquidation. Close your positions or add additional margin to your wallet.")
                    self.logger().info(f"Margin Required: {total_maint_margin_required}. Total Unrealized PnL: "
                                       f"{negative_pnls_msg}. Negative PnL assets: {negative_pnls_msg}.")
            except asyncio.CancelledError:
                raise
            except Exception as e:
                self.logger().error(f"Unexpected error in user stream listener loop: {e}", exc_info=True)
                await asyncio.sleep(5.0)
Example #29
0
    async def create_order(self,
                           trade_type: TradeType,
                           order_id: str,
                           trading_pair: str,
                           amount: Decimal,
                           order_type: OrderType,
                           position_action: PositionAction,
                           price: Optional[Decimal] = Decimal("NaN")):

        trading_rule: TradingRule = self._trading_rules[trading_pair]
        if position_action not in [PositionAction.OPEN, PositionAction.CLOSE]:
            raise ValueError("Specify either OPEN_POSITION or CLOSE_POSITION position_action.")

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

        if amount < trading_rule.min_order_size:
            raise ValueError(f"Buy order amount {amount} is lower than the minimum order size "
                             f"{trading_rule.min_order_size}")

        order_result = None
        api_params = {"symbol": convert_to_exchange_trading_pair(trading_pair),
                      "side": "BUY" if trade_type is TradeType.BUY else "SELL",
                      "type": "LIMIT" if order_type is OrderType.LIMIT else "MARKET",
                      "quantity": f"{amount}",
                      "newClientOrderId": order_id
                      }
        if order_type == OrderType.LIMIT:
            api_params["price"] = f"{price}"
            api_params["timeInForce"] = "GTC"

        if self._position_mode == PositionMode.HEDGE:
            if position_action == PositionAction.OPEN:
                api_params["positionSide"] = "LONG" if trade_type is TradeType.BUY else "SHORT"
            else:
                api_params["positionSide"] = "SHORT" if trade_type is TradeType.BUY else "LONG"

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

        try:
            order_result = await self.request(path="/fapi/v1/order",
                                              params=api_params,
                                              method=MethodType.POST,
                                              add_timestamp = True,
                                              is_signed=True)
            exchange_order_id = str(order_result["orderId"])
            tracked_order = self._in_flight_orders.get(order_id)
            if tracked_order is not None:
                self.logger().info(f"Created {order_type.name.lower()} {trade_type.name.lower()} order {order_id} for "
                                   f"{amount} {trading_pair}.")
                tracked_order.exchange_order_id = exchange_order_id

            event_tag = self.MARKET_BUY_ORDER_CREATED_EVENT_TAG if trade_type is TradeType.BUY \
                else self.MARKET_SELL_ORDER_CREATED_EVENT_TAG
            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))
            return order_result
        except asyncio.CancelledError:
            raise
        except Exception as e:
            self.stop_tracking_order(order_id)
            self.logger().network(
                f"Error submitting order to Binance Perpetuals for {amount} {trading_pair} "
                f"{'' if order_type is OrderType.MARKET else price}.",
                exc_info=True,
                app_warning_msg=str(e)
            )
            self.trigger_event(self.MARKET_ORDER_FAILURE_EVENT_TAG,
                               MarketOrderFailureEvent(self.current_timestamp, order_id, order_type))
    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)
        """
        exchange_order_id = order_msg.orderId
        client_order_id = None

        for in_flight_order in self._in_flight_orders.values():
            if in_flight_order.exchange_order_id == exchange_order_id:
                client_order_id = in_flight_order.client_order_id

        if client_order_id is None:
            return

        tracked_order: AscendExInFlightOrder = self._in_flight_orders[
            client_order_id]
        # This could happen for Ack request type when placing new order, we don't know if the order is open until
        # we get order status update
        if tracked_order.is_locally_new and AscendExInFlightOrder.is_open_status(
                order_msg.status):
            self.trigger_order_created_event(tracked_order)
        tracked_order.update_status(order_msg.status)

        if tracked_order.executed_amount_base != Decimal(
                order_msg.cumFilledQty):
            # Update the relevant order information when there is fill event
            new_filled_amount = Decimal(
                order_msg.cumFilledQty) - tracked_order.executed_amount_base
            new_fee_paid = Decimal(order_msg.cumFee) - tracked_order.fee_paid

            tracked_order.executed_amount_base = Decimal(
                order_msg.cumFilledQty)
            tracked_order.executed_amount_quote = Decimal(
                order_msg.avgPx) * tracked_order.executed_amount_base
            tracked_order.fee_paid = Decimal(order_msg.cumFee)
            tracked_order.fee_asset = order_msg.feeAsset

            self.trigger_event(
                MarketEvent.OrderFilled,
                OrderFilledEvent(
                    self.current_timestamp, client_order_id,
                    tracked_order.trading_pair,
                    tracked_order.trade_type, tracked_order.order_type,
                    Decimal(order_msg.avgPx), new_filled_amount,
                    TradeFee(0.0, [(tracked_order.fee_asset, new_fee_paid)]),
                    exchange_order_id))

        if tracked_order.is_cancelled:
            self.logger().info(
                f"Successfully cancelled order {client_order_id}.")
            self.trigger_event(
                MarketEvent.OrderCancelled,
                OrderCancelledEvent(self.current_timestamp, client_order_id,
                                    exchange_order_id))
            tracked_order.cancelled_event.set()
            self.stop_tracking_order(client_order_id)
        elif tracked_order.is_failure:
            self.logger().info(
                f"Order {client_order_id} has failed according to order status API. "
                f"API order response: {order_msg}")
            self.trigger_event(
                MarketEvent.OrderFailure,
                MarketOrderFailureEvent(self.current_timestamp,
                                        client_order_id,
                                        tracked_order.order_type))
            self.stop_tracking_order(client_order_id)
        elif tracked_order.is_filled:
            event_tag = MarketEvent.BuyOrderCompleted if tracked_order.trade_type is TradeType.BUY else MarketEvent.SellOrderCompleted
            event_class = BuyOrderCompletedEvent if tracked_order.trade_type is TradeType.BUY else SellOrderCompletedEvent
            self.trigger_event(
                event_tag,
                event_class(self.current_timestamp, client_order_id,
                            tracked_order.base_asset,
                            tracked_order.quote_asset, tracked_order.fee_asset,
                            tracked_order.executed_amount_base,
                            tracked_order.executed_amount_quote,
                            tracked_order.fee_paid, tracked_order.order_type,
                            exchange_order_id))
            self.stop_tracking_order(client_order_id)