Esempio n. 1
0
    async def update_canceling_transactions(
            self, canceled_tracked_orders: List[GatewayInFlightOrder]):
        """
        Update tracked orders that have a cancel_tx_hash.
        :param canceled_tracked_orders: Canceled tracked_orders (cancel_tx_has is not None).
        """
        if len(canceled_tracked_orders) < 1:
            return

        self.logger().debug(
            "Polling for order status updates of %d canceled orders.",
            len(canceled_tracked_orders))
        update_results: List[Union[Dict[
            str, Any], Exception]] = await safe_gather(*[
                self._get_gateway_instance().get_transaction_status(
                    self.chain, self.network, tx_hash) for tx_hash in
                [t.cancel_tx_hash for t in canceled_tracked_orders]
            ],
                                                       return_exceptions=True)
        for tracked_order, update_result in zip(canceled_tracked_orders,
                                                update_results):
            if isinstance(update_result, Exception):
                raise update_result
            if "txHash" not in update_result:
                self.logger().error(
                    f"No txHash field for transaction status of {tracked_order.client_order_id}: "
                    f"{update_result}.")
                continue
            if update_result["txStatus"] == 1:
                if update_result["txReceipt"]["status"] == 1:
                    if tracked_order.current_state == OrderState.PENDING_CANCEL:
                        if not tracked_order.is_approval_request:
                            order_update: OrderUpdate = OrderUpdate(
                                trading_pair=tracked_order.trading_pair,
                                client_order_id=tracked_order.client_order_id,
                                update_timestamp=self.current_timestamp,
                                new_state=OrderState.CANCELED)
                            self._order_tracker.process_order_update(
                                order_update)

                        elif tracked_order.is_approval_request:
                            order_update: OrderUpdate = OrderUpdate(
                                trading_pair=tracked_order.trading_pair,
                                client_order_id=tracked_order.client_order_id,
                                update_timestamp=self.current_timestamp,
                                new_state=OrderState.CANCELED)
                            token_symbol: str = self.get_token_symbol_from_approval_order_id(
                                tracked_order.client_order_id)
                            self.trigger_event(
                                TokenApprovalEvent.ApprovalCancelled,
                                TokenApprovalCancelledEvent(
                                    self.current_timestamp,
                                    self.connector_name, token_symbol))
                            self.logger().info(
                                f"Token approval for {tracked_order.client_order_id} on "
                                f"{self.connector_name} has been canceled.")
                            self.stop_tracking_order(
                                tracked_order.client_order_id)
Esempio n. 2
0
    def test_order_life_cycle_of_token_approval_requests(self):
        order: GatewayInFlightOrder = GatewayInFlightOrder(
            client_order_id=self.client_order_id,
            trading_pair=self.quote_asset,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            creation_timestamp=1652324823,
            initial_state=OrderState.PENDING_APPROVAL,
        )
        # Assert that order is in fact a Approval Request
        self.assertTrue(order.is_approval_request)

        self.assertTrue(order.is_pending_approval)

        order_update: OrderUpdate = OrderUpdate(
            trading_pair=order.trading_pair,
            update_timestamp=1652324824,
            new_state=OrderState.APPROVED,
            client_order_id=order.client_order_id,
            exchange_order_id=self.exchange_order_id,
        )

        order.update_with_order_update(order_update=order_update)

        self.assertFalse(order.is_pending_approval)
Esempio n. 3
0
    async def _execute_cancel(self, client_order_id: str) -> Dict[str, Any]:
        """
        Requests the exchange to cancel an active order
        :param client_order_id: the client id of the order to cancel
        """
        tracked_order = self._order_tracker.fetch_tracked_order(
            client_order_id)
        if tracked_order is not None:
            try:
                api_params = {
                    "orderLinkId": client_order_id,
                }
                cancel_result = await self._api_request(
                    method=RESTMethod.DELETE,
                    path_url=CONSTANTS.ORDER_PATH_URL,
                    params=api_params,
                    is_auth_required=True)

                order_update: OrderUpdate = OrderUpdate(
                    client_order_id=client_order_id,
                    trading_pair=tracked_order.trading_pair,
                    update_timestamp=self.current_timestamp,
                    new_state=OrderState.CANCELED,
                )
                self._order_tracker.process_order_update(order_update)
                return cancel_result

            except asyncio.CancelledError:
                raise
            except Exception:
                self.logger().network(
                    f"There was a an error when requesting cancellation of order {client_order_id}"
                )
                raise
    def _process_order_message(self, order_msg: Dict[str, Any]):
        """
        Updates in-flight order and triggers cancelation 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:
        https://www.gate.io/docs/apiv4/en/#list-orders
        """
        state = None
        client_order_id = str(order_msg.get("text", ""))
        tracked_order = self.in_flight_orders.get(client_order_id, None)
        if not tracked_order:
            self.logger().debug(
                f"Ignoring order message with id {client_order_id}: not in in_flight_orders."
            )
            return

        state = self._normalise_order_message_state(order_msg, tracked_order)
        if state:
            order_update = OrderUpdate(
                trading_pair=tracked_order.trading_pair,
                update_timestamp=int(order_msg["update_time"]),
                new_state=state,
                client_order_id=client_order_id,
                exchange_order_id=str(order_msg["id"]),
            )
            self._order_tracker.process_order_update(order_update=order_update)
            self.logger().info(
                f"Successfully updated order {tracked_order.client_order_id}.")
    def test_update_with_order_update_client_order_id_mismatch(self):
        order: InFlightOrder = InFlightOrder(
            client_order_id=self.client_order_id,
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            price=Decimal("1.0"),
        )

        mismatch_order_update: OrderUpdate = OrderUpdate(
            client_order_id="mismatchClientOrderId",
            exchange_order_id="mismatchExchangeOrderId",
            trading_pair=self.trading_pair,
            update_timestamp=1,
            new_state=OrderState.OPEN,
            fill_price=Decimal("1.0"),
            executed_amount_base=Decimal("1000.0"),
            executed_amount_quote=Decimal("1000.0"),
            fee_asset=self.base_asset,
            cumulative_fee_paid=self.trade_fee_percent * Decimal("1000.0"),
        )

        self.assertFalse(order.update_with_order_update(mismatch_order_update))
        self.assertEqual(Decimal("0"), order.executed_amount_base)
        self.assertEqual(Decimal("0"), order.executed_amount_quote)
        self.assertIsNone(order.fee_asset)
        self.assertEqual(Decimal("0"), order.cumulative_fee_paid)
        self.assertEqual(Decimal("0"), order.last_filled_price)
        self.assertEqual(Decimal("0"), order.last_filled_amount)
        self.assertEqual(Decimal("0"), order.last_fee_paid)
        self.assertEqual(-1, order.last_update_timestamp)
    def test_update_with_order_update_open_order(self):
        order: InFlightOrder = InFlightOrder(
            client_order_id=self.client_order_id,
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            price=Decimal("1.0"),
        )

        open_order_update: OrderUpdate = OrderUpdate(
            client_order_id=self.client_order_id,
            exchange_order_id=self.exchange_order_id,
            trading_pair=self.trading_pair,
            update_timestamp=1,
            new_state=OrderState.OPEN,
        )

        self.assertTrue(order.update_with_order_update(open_order_update))
        self.assertEqual(Decimal("0"), order.executed_amount_base)
        self.assertEqual(Decimal("0"), order.executed_amount_quote)
        self.assertIsNone(order.fee_asset)
        self.assertEqual(Decimal("0"), order.cumulative_fee_paid)
        self.assertEqual(Decimal("0"), order.last_filled_price)
        self.assertEqual(Decimal("0"), order.last_filled_amount)
        self.assertEqual(Decimal("0"), order.last_fee_paid)
        self.assertEqual(1, order.last_update_timestamp)
Esempio n. 7
0
    async def process_order_not_found(self, client_order_id: str):
        """
        Increments and checks if the order specified has exceeded the ORDER_NOT_FOUND_COUNT_LIMIT.
        A failed event is triggered if necessary.

        :param client_order_id: Client order id of an order.
        :type client_order_id: str
        """
        # Only concerned with active orders.
        tracked_order: Optional[InFlightOrder] = self.fetch_tracked_order(
            client_order_id=client_order_id)

        if tracked_order is None:
            self.logger().debug(
                f"Order is not/no longer being tracked ({client_order_id})")
        else:
            self._order_not_found_records[client_order_id] += 1
            if self._order_not_found_records[
                    client_order_id] > self.ORDER_NOT_FOUND_COUNT_LIMIT:
                # Only mark the order as failed if it has not been marked as done already asynchronously
                if not tracked_order.is_done:
                    order_update: OrderUpdate = OrderUpdate(
                        client_order_id=client_order_id,
                        trading_pair=tracked_order.trading_pair,
                        update_timestamp=self.current_timestamp,
                        new_state=OrderState.FAILED,
                    )
                    await self._process_order_update(order_update)
Esempio n. 8
0
    async def _process_order_update(self, order: Dict[str, Any]):
        symbol = f"{order['baseCurrency']}/{order['quoteCurrency']}"
        trading_pair = await self.trading_pair_associated_to_exchange_symbol(
            symbol=symbol)
        client_order_id = order['clientOrderId']

        change_type = order['changeType']
        status = order['status']
        quantity = Decimal(order["quantity"])
        filled = Decimal(order['filled'])
        delta_filled = Decimal(order['deltaFilled'])

        state = web_utils.get_order_status_ws(change_type=change_type,
                                              status=status,
                                              quantity=quantity,
                                              filled=filled,
                                              delta_filled=delta_filled)
        if state is None:
            return

        timestamp = float(order["timestamp"]) * 1e-3

        order_update = OrderUpdate(
            trading_pair=trading_pair,
            update_timestamp=timestamp,
            new_state=state,
            client_order_id=client_order_id,
        )

        self._order_tracker.process_order_update(order_update=order_update)
Esempio n. 9
0
    def test_process_order_update_trigger_order_cancelled_event(self):
        order: InFlightOrder = InFlightOrder(
            client_order_id="someClientOrderId",
            exchange_order_id="someExchangeOrderId",
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            creation_timestamp=1640001112.0,
            price=Decimal("1.0"),
            initial_state=OrderState.OPEN,
        )

        self.tracker.start_tracking_order(order)

        order_cancelled_update: OrderUpdate = OrderUpdate(
            client_order_id=order.client_order_id,
            exchange_order_id=order.exchange_order_id,
            trading_pair=self.trading_pair,
            update_timestamp=1,
            new_state=OrderState.CANCELLED,
        )

        update_future = self.tracker.process_order_update(order_cancelled_update)
        self.async_run_with_timeout(update_future)

        self.assertTrue(self._is_logged("INFO", f"Successfully cancelled order {order.client_order_id}."))
        self.assertEqual(0, len(self.tracker.active_orders))
        self.assertEqual(1, len(self.tracker.cached_orders))
        self.assertEqual(1, len(self.order_cancelled_logger.event_log))

        event_triggered = self.order_cancelled_logger.event_log[0]
        self.assertIsInstance(event_triggered, OrderCancelledEvent)
        self.assertEqual(event_triggered.exchange_order_id, order.exchange_order_id)
        self.assertEqual(event_triggered.order_id, order.client_order_id)
Esempio n. 10
0
 def _update_order_after_failure(self, order_id: str, trading_pair: str):
     order_update: OrderUpdate = OrderUpdate(
         client_order_id=order_id,
         trading_pair=trading_pair,
         update_timestamp=self.current_timestamp,
         new_state=OrderState.FAILED,
     )
     self._order_tracker.process_order_update(order_update)
    def test_process_order_update_trigger_order_creation_event_without_client_order_id(
            self):
        order: InFlightOrder = InFlightOrder(
            client_order_id="someClientOrderId",
            exchange_order_id=
            "someExchangeOrderId",  # exchange_order_id is provided when initialized. See AscendEx.
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            creation_timestamp=1640001112.0,
            price=Decimal("1.0"),
        )
        self.tracker.start_tracking_order(order)

        order_creation_update: OrderUpdate = OrderUpdate(
            # client_order_id=order.client_order_id,  # client_order_id purposefully ommited
            exchange_order_id="someExchangeOrderId",
            trading_pair=self.trading_pair,
            update_timestamp=1,
            new_state=OrderState.OPEN,
        )

        update_future = self.tracker.process_order_update(
            order_creation_update)
        self.async_run_with_timeout(update_future)

        updated_order: InFlightOrder = self.tracker.fetch_tracked_order(
            order.client_order_id)

        # Check order update has been successfully applied
        self.assertEqual(updated_order.exchange_order_id,
                         order_creation_update.exchange_order_id)
        self.assertTrue(updated_order.exchange_order_id_update_event.is_set())
        self.assertEqual(updated_order.current_state,
                         order_creation_update.new_state)
        self.assertTrue(updated_order.is_open)

        # Check that Logger has logged the correct log
        self.assertTrue(
            self._is_logged(
                "INFO",
                f"Created {order.order_type.name} {order.trade_type.name} order {order.client_order_id} for "
                f"{order.amount} {order.trading_pair}.",
            ))

        # Check that Buy/SellOrderCreatedEvent has been triggered.
        self.assertEqual(1, len(self.buy_order_created_logger.event_log))
        event_logged = self.buy_order_created_logger.event_log[0]

        self.assertEqual(event_logged.amount, order.amount)
        self.assertEqual(event_logged.exchange_order_id,
                         order_creation_update.exchange_order_id)
        self.assertEqual(event_logged.order_id, order.client_order_id)
        self.assertEqual(event_logged.price, order.price)
        self.assertEqual(event_logged.trading_pair, order.trading_pair)
        self.assertEqual(event_logged.type, order.order_type)
    def test_updating_order_states_with_both_process_order_update_and_process_trade_update(
            self):
        order: InFlightOrder = InFlightOrder(
            client_order_id="someClientOrderId",
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            creation_timestamp=1640001112.0,
            price=Decimal("1.0"),
        )
        self.tracker.start_tracking_order(order)

        order_creation_update: OrderUpdate = OrderUpdate(
            client_order_id=order.client_order_id,
            exchange_order_id="someExchangeOrderId",
            trading_pair=self.trading_pair,
            update_timestamp=1,
            new_state=OrderState.OPEN,
        )

        update_future = self.tracker.process_order_update(
            order_creation_update)
        self.async_run_with_timeout(update_future)

        open_order: InFlightOrder = self.tracker.fetch_tracked_order(
            order.client_order_id)

        # Check order_creation_update has been successfully applied
        self.assertEqual(open_order.exchange_order_id,
                         order_creation_update.exchange_order_id)
        self.assertTrue(open_order.exchange_order_id_update_event.is_set())
        self.assertEqual(open_order.current_state,
                         order_creation_update.new_state)
        self.assertTrue(open_order.is_open)
        self.assertEqual(0, len(open_order.order_fills))

        trade_filled_price: Decimal = order.price
        trade_filled_amount: Decimal = order.amount
        fee_paid: Decimal = self.trade_fee_percent * trade_filled_amount
        trade_update: TradeUpdate = TradeUpdate(
            trade_id=1,
            client_order_id=order.client_order_id,
            exchange_order_id=order.exchange_order_id,
            trading_pair=order.trading_pair,
            fill_price=trade_filled_price,
            fill_base_amount=trade_filled_amount,
            fill_quote_amount=trade_filled_price * trade_filled_amount,
            fee=AddedToCostTradeFee(flat_fees=[
                TokenAmount(token=self.quote_asset, amount=fee_paid)
            ]),
            fill_timestamp=2,
        )

        self.tracker.process_trade_update(trade_update)
        self.assertEqual(1, len(self.tracker.active_orders))
        self.assertEqual(0, len(self.tracker.cached_orders))
Esempio n. 13
0
    async def _execute_cancel(self, trading_pair: str, order_id: str):
        """
        Requests the exchange to cancel an active order
        :param trading_pair: the trading pair the order to cancel operates with
        :param order_id: the client id of the order to cancel
        """
        tracked_order = self._order_tracker.fetch_tracked_order(order_id)
        if tracked_order is not None:
            try:
                symbol = await CoinflexAPIOrderBookDataSource.exchange_symbol_associated_to_pair(
                    trading_pair=trading_pair,
                    domain=self._domain,
                    api_factory=self._api_factory,
                    throttler=self._throttler)
                api_params = {
                    "responseType": "FULL",
                }
                cancel_params = {
                    "marketCode": symbol,
                    "clientOrderId": order_id,
                }
                api_params["orders"] = [cancel_params]
                try:
                    result = await self._api_request(
                        method=RESTMethod.DELETE,
                        path_url=CONSTANTS.ORDER_CANCEL_PATH_URL,
                        data=api_params,
                        is_auth_required=True)
                    cancel_result = result["data"][0]
                except web_utils.CoinflexAPIError as e:
                    # Catch order not found as cancelled.
                    result = {}
                    cancel_result = {}
                    if e.error_payload.get("errors") in CONSTANTS.ORDER_NOT_FOUND_ERRORS:
                        cancel_result = e.error_payload["data"][0]
                    else:
                        self.logger().error(f"Unhandled error canceling order: {order_id}. Error: {e.error_payload}", exc_info=True)

                if cancel_result.get("status", result.get("event")) in CONSTANTS.ORDER_CANCELED_STATES:
                    cancelled_timestamp = cancel_result.get("timestamp", result.get("timestamp"))
                    order_update: OrderUpdate = OrderUpdate(
                        client_order_id=order_id,
                        trading_pair=tracked_order.trading_pair,
                        update_timestamp=int(cancelled_timestamp) * 1e-3 if cancelled_timestamp else self.current_timestamp,
                        new_state=OrderState.CANCELED,
                    )
                    self._order_tracker.process_order_update(order_update)
                else:
                    if not self._process_order_not_found(order_id, tracked_order):
                        raise IOError
                return cancel_result

            except asyncio.CancelledError:
                raise
            except Exception:
                self.logger().exception(f"There was an error when requesting cancelation of order {order_id}")
Esempio n. 14
0
    async def _update_order_status(self):
        # This is intended to be a backup measure to close straggler orders, in case Binance's user stream events
        # are not working.
        # The minimum poll interval for order status is 10 seconds.
        last_tick = self._last_poll_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL
        current_tick = self.current_timestamp / self.UPDATE_ORDER_STATUS_MIN_INTERVAL

        tracked_orders: List[InFlightOrder] = list(
            self.in_flight_orders.values())
        if current_tick > last_tick and len(tracked_orders) > 0:

            tasks = [
                self._api_get(path_url=CONSTANTS.ORDER_PATH_URL,
                              params={
                                  "symbol":
                                  await
                                  self.exchange_symbol_associated_to_pair(
                                      trading_pair=o.trading_pair),
                                  "origClientOrderId":
                                  o.client_order_id
                              },
                              is_auth_required=True) for o 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 the order has already been canceled or has failed do nothing
                if client_order_id not in self.in_flight_orders:
                    continue

                if isinstance(order_update, Exception):
                    self.logger().network(
                        f"Error fetching status update for the order {client_order_id}: {order_update}.",
                        app_warning_msg=
                        f"Failed to fetch status update for the order {client_order_id}."
                    )
                    # 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
                    await self._order_tracker.process_order_not_found(
                        client_order_id)

                else:
                    # Update order execution status
                    new_state = CONSTANTS.ORDER_STATE[order_update["status"]]

                    update = OrderUpdate(
                        client_order_id=client_order_id,
                        exchange_order_id=str(order_update["orderId"]),
                        trading_pair=tracked_order.trading_pair,
                        update_timestamp=order_update["updateTime"] * 1e-3,
                        new_state=new_state,
                    )
                    self._order_tracker.process_order_update(update)
Esempio n. 15
0
    def test_update_with_order_update_multiple_order_updates(self):
        order: InFlightOrder = InFlightOrder(
            client_order_id=self.client_order_id,
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            creation_timestamp=1640001112.0,
            price=Decimal("1.0"),
        )

        order_update_1: OrderUpdate = OrderUpdate(
            client_order_id=self.client_order_id,
            exchange_order_id=self.exchange_order_id,
            trading_pair=self.trading_pair,
            update_timestamp=1,
            new_state=OrderState.PARTIALLY_FILLED,
        )

        self.assertTrue(order.update_with_order_update(order_update_1))
        # Order updates should not modify executed values
        self.assertEqual(Decimal(0), order.executed_amount_base)
        self.assertEqual(Decimal(0), order.executed_amount_quote)
        self.assertEqual(order.last_update_timestamp, 1)
        self.assertEqual(0, len(order.order_fills))
        self.assertTrue(order.is_open)

        order_update_2: OrderUpdate = OrderUpdate(
            client_order_id=self.client_order_id,
            exchange_order_id=self.exchange_order_id,
            trading_pair=self.trading_pair,
            update_timestamp=2,
            new_state=OrderState.FILLED,
        )

        self.assertTrue(order.update_with_order_update(order_update_2))
        # Order updates should not modify executed values
        self.assertEqual(Decimal(0), order.executed_amount_base)
        self.assertEqual(Decimal(0), order.executed_amount_quote)
        self.assertEqual(order.last_update_timestamp, 2)
        self.assertEqual(0, len(order.order_fills))
        self.assertTrue(order.is_done)
 async def _process_order_update(self, order: InFlightOrder,
                                 order_update: Dict[str, Any]):
     order_data = order_update["data"]
     new_state = CONSTANTS.ORDER_STATE[order_data["status"]]
     update = OrderUpdate(
         client_order_id=order.client_order_id,
         exchange_order_id=str(order_data["order_id"]),
         trading_pair=order.trading_pair,
         update_timestamp=self.current_timestamp,
         new_state=new_state,
     )
     self._order_tracker.process_order_update(update)
Esempio n. 17
0
    async def _execute_cancel(self, order_id: str,
                              cancel_age: int) -> Optional[str]:
        """
        Cancel an existing order if the age of the order is greater than its cancel_age,
        and if the order is not done or already in the cancelling state.
        """
        try:
            tracked_order: GatewayInFlightOrder = self._order_tracker.fetch_order(
                client_order_id=order_id)
            if tracked_order is None:
                self.logger().error(
                    f"The order {order_id} is not being tracked.")
                raise ValueError(f"The order {order_id} is not being tracked.")

            if (self.current_timestamp -
                    tracked_order.creation_timestamp) < cancel_age:
                return None

            if tracked_order.is_done:
                return None

            if tracked_order.is_pending_cancel_confirmation:
                return order_id

            self.logger().info(
                f"The blockchain transaction for {order_id} with nonce {tracked_order.nonce} has "
                f"expired. Canceling the order...")
            resp: Dict[str, Any] = await self._get_gateway_instance(
            ).cancel_evm_transaction(self.chain, self.network, self.address,
                                     tracked_order.nonce)

            tx_hash: Optional[str] = resp.get("txHash")
            if tx_hash is not None:
                tracked_order.cancel_tx_hash = tx_hash
            else:
                raise EnvironmentError(
                    f"Missing txHash from cancel_evm_transaction() response: {resp}."
                )

            order_update: OrderUpdate = OrderUpdate(
                client_order_id=order_id,
                trading_pair=tracked_order.trading_pair,
                update_timestamp=self.current_timestamp,
                new_state=OrderState.PENDING_CANCEL)
            self._order_tracker.process_order_update(order_update)

            return order_id
        except asyncio.CancelledError:
            raise
        except Exception as err:
            self.logger().error(
                f"Failed to cancel order {order_id}: {str(err)}.",
                exc_info=True)
    def test_process_order_update_trigger_order_creation_event(self):
        order: InFlightOrder = InFlightOrder(
            client_order_id="someClientOrderId",
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            price=Decimal("1.0"),
        )
        self.tracker.start_tracking_order(order)

        order_creation_update: OrderUpdate = OrderUpdate(
            client_order_id=order.client_order_id,
            exchange_order_id="someExchangeOrderId",
            trading_pair=self.trading_pair,
            update_timestamp=1,
            new_state=OrderState.OPEN,
        )

        self.tracker.process_order_update(order_creation_update)

        updated_order: InFlightOrder = self.tracker.fetch_tracked_order(
            order.client_order_id)

        # Check order update has been successfully applied
        self.assertEqual(updated_order.exchange_order_id,
                         order_creation_update.exchange_order_id)
        self.assertTrue(updated_order.exchange_order_id_update_event.is_set())
        self.assertEqual(updated_order.current_state,
                         order_creation_update.new_state)
        self.assertTrue(updated_order.is_open)

        # Check that Logger has logged the correct log
        self.assertTrue(
            self._is_logged(
                "INFO",
                f"Created {order.order_type.name} {order.trade_type.name} order {order.client_order_id} for "
                f"{order.amount} {order.trading_pair}.",
            ))

        # Check that Buy/SellOrderCreatedEvent has been triggered.
        self.assertEqual(1, len(self.connector.event_logs))
        event_logged = self.connector.event_logs[0]

        self.assertIsInstance(event_logged, BuyOrderCreatedEvent)
        self.assertEqual(event_logged.amount, order.amount)
        self.assertEqual(event_logged.exchange_order_id,
                         order_creation_update.exchange_order_id)
        self.assertEqual(event_logged.order_id, order.client_order_id)
        self.assertEqual(event_logged.price, order.price)
        self.assertEqual(event_logged.trading_pair, order.trading_pair)
        self.assertEqual(event_logged.type, order.order_type)
Esempio n. 19
0
    async def _update_order_status(self):
        # This is intended to be a backup measure to close straggler orders, in case Latoken's user stream events
        # are not working.
        # The minimum poll interval for order status is 10 seconds.
        last_tick = self._last_poll_timestamp / CONSTANTS.UPDATE_ORDER_STATUS_MIN_INTERVAL
        current_tick = self.current_timestamp / CONSTANTS.UPDATE_ORDER_STATUS_MIN_INTERVAL

        tracked_orders: List[InFlightOrder] = list(
            self.in_flight_orders.values())

        if current_tick <= last_tick or len(tracked_orders) == 0:
            return

        update_to_tracked = await self._update_to_tracked_order(
            tracked_orders=tracked_orders)
        for order_update, tracked_order in zip(*update_to_tracked):

            client_order_id = tracked_order.client_order_id

            # If the order has already been cancelled or has failed do nothing
            if client_order_id not in self.in_flight_orders:
                continue

            if isinstance(order_update, Exception):
                self.logger().network(
                    f"Error fetching status update for the order {client_order_id}: {order_update}.",
                    app_warning_msg=
                    f"Failed to fetch status update for the order {client_order_id}."
                )
                # 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
                await self._order_tracker.process_order_not_found(
                    client_order_id=client_order_id)
            else:
                # Update order execution status
                status = order_update["status"]
                filled = Decimal(order_update["filled"])
                quantity = Decimal(order_update["quantity"])

                new_state = web_utils.get_order_status_rest(status=status,
                                                            filled=filled,
                                                            quantity=quantity)

                update = OrderUpdate(
                    client_order_id=client_order_id,
                    exchange_order_id=order_update["id"],
                    trading_pair=tracked_order.trading_pair,
                    update_timestamp=float(order_update["timestamp"]) * 1e-3,
                    new_state=new_state,
                )
                self._order_tracker.process_order_update(update)
Esempio n. 20
0
    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)
    def test_process_order_update_invalid_order_update(self):

        order_creation_update: OrderUpdate = OrderUpdate(
            # client_order_id="someClientOrderId",  # client_order_id intentionally omitted
            # exchange_order_id="someExchangeOrderId",  # client_order_id intentionally omitted
            trading_pair=self.trading_pair,
            update_timestamp=1,
            new_state=OrderState.OPEN,
        )

        self.tracker.process_order_update(order_creation_update)

        self.assertTrue(
            self._is_logged(
                "ERROR",
                "OrderUpdate does not contain any client_order_id or exchange_order_id",
            ))
    def test_process_order_update_order_not_found(self):

        order_creation_update: OrderUpdate = OrderUpdate(
            client_order_id="someClientOrderId",
            exchange_order_id="someExchangeOrderId",
            trading_pair=self.trading_pair,
            update_timestamp=1,
            new_state=OrderState.OPEN,
        )

        self.tracker.process_order_update(order_creation_update)

        self.assertTrue(
            self._is_logged(
                "DEBUG",
                f"Order is not/no longer being tracked ({order_creation_update})",
            ))
Esempio n. 23
0
    def _process_order_not_found(self, client_order_id: str,
                                 tracked_order: InFlightOrder) -> bool:
        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.MAX_ORDER_UPDATE_RETRIEVAL_RETRIES_WITH_FAILURES):
            # 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

            order_update: OrderUpdate = OrderUpdate(
                client_order_id=client_order_id,
                trading_pair=tracked_order.trading_pair,
                update_timestamp=self.current_timestamp,
                new_state=OrderState.FAILED,
            )
            self._order_tracker.process_order_update(order_update)
            return True
        return False
Esempio n. 24
0
 def _stop_tracking_order_exceed_no_exchange_id_limit(self, tracked_order: InFlightOrder):
     """
     Increments and checks if the tracked order has exceed the STOP_TRACKING_ORDER_NOT_FOUND_LIMIT limit.
     If true, Triggers a MarketOrderFailureEvent and stops tracking the order.
     """
     client_order_id = tracked_order.client_order_id
     self._order_without_exchange_id_records[client_order_id] = (
         self._order_without_exchange_id_records.get(client_order_id, 0) + 1)
     if self._order_without_exchange_id_records[client_order_id] >= self.STOP_TRACKING_ORDER_NOT_FOUND_LIMIT:
         # Wait until the absence of exchange id has repeated a few times before actually treating it as failed.
         order_update = OrderUpdate(
             trading_pair=tracked_order.trading_pair,
             client_order_id=tracked_order.client_order_id,
             update_timestamp=time.time(),
             new_state=OrderState.FAILED,
         )
         self._in_flight_order_tracker.process_order_update(order_update)
         del self._order_without_exchange_id_records[client_order_id]
Esempio n. 25
0
    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)
Esempio n. 26
0
    async def _execute_cancel(self, trading_pair: str, order_id: str):
        """
        Requests the exchange to cancel an active order
        :param trading_pair: the trading pair the order to cancel operates with
        :param order_id: the client id of the order to cancel
        """
        tracked_order = self._order_tracker.fetch_tracked_order(order_id)
        if tracked_order is not None:
            try:
                symbol = await BinanceAPIOrderBookDataSource.exchange_symbol_associated_to_pair(
                    trading_pair=trading_pair,
                    domain=self._domain,
                    api_factory=self._api_factory,
                    throttler=self._throttler)
                api_params = {
                    "symbol": symbol,
                    "origClientOrderId": order_id,
                }
                cancel_result = await self._api_request(
                    method=RESTMethod.DELETE,
                    path_url=CONSTANTS.ORDER_PATH_URL,
                    params=api_params,
                    is_auth_required=True)

                if cancel_result.get("status") == "CANCELED":
                    order_update: OrderUpdate = OrderUpdate(
                        client_order_id=order_id,
                        trading_pair=tracked_order.trading_pair,
                        update_timestamp=int(self.current_timestamp * 1e3),
                        new_state=OrderState.CANCELLED,
                    )
                    self._order_tracker.process_order_update(order_update)
                return cancel_result

            except asyncio.CancelledError:
                raise
            except Exception:
                self.logger().exception(
                    f"There was a an error when requesting cancellation of order {order_id}"
                )
                raise
Esempio n. 27
0
    async def _execute_cancel(self, trading_pair: str, order_id: str) -> str:
        """
        Requests the exchange to cancel an active order

        :param trading_pair: the trading pair the order to cancel operates with
        :param order_id: the client id of the order to cancel
        """
        tracked_order = self._order_tracker.fetch_tracked_order(order_id)
        if tracked_order is not None:
            try:
                exchange_order_id = await tracked_order.get_exchange_order_id()
                path_url = f"{CONSTANTS.ORDERS_PATH_URL}/{exchange_order_id}"
                cancel_result = await self._api_request(
                    path_url=path_url,
                    method=RESTMethod.DELETE,
                    is_auth_required=True,
                    limit_id=CONSTANTS.DELETE_ORDER_LIMIT_ID
                )

                if tracked_order.exchange_order_id in cancel_result["data"].get("cancelledOrderIds", []):
                    order_update: OrderUpdate = OrderUpdate(
                        client_order_id=order_id,
                        trading_pair=tracked_order.trading_pair,
                        update_timestamp=self.current_timestamp,
                        new_state=OrderState.CANCELED,
                    )
                    self._order_tracker.process_order_update(order_update)
                    return order_id
            except asyncio.CancelledError:
                raise
            except asyncio.TimeoutError:
                self.logger().warning(f"Failed to cancel the order {order_id} because it does not have an exchange"
                                      f" order id yet")
                await self._order_tracker.process_order_not_found(order_id)
            except Exception as e:
                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 Kucoin. "
                                    f"Check API key and network connection."
                )
Esempio n. 28
0
    async def _user_stream_event_listener(self):
        """
        This functions runs in background continuously processing the events received from the exchange by the user
        stream data source. It keeps reading events from the queue until the task is interrupted.
        The events received are balance updates, order updates and trade events.
        """
        async for event_message in self._iter_user_event_queue():
            try:
                event_type = event_message.get("table")
                if event_type == "order":
                    order_data = event_message["data"][0]
                    client_order_id = order_data.get("clientOrderId")

                    tracked_order = self.in_flight_orders.get(client_order_id)
                    if not tracked_order:
                        return
                    try:
                        await tracked_order.get_exchange_order_id()
                    except asyncio.TimeoutError:
                        self.logger().error(f"Failed to get exchange order id for order: {tracked_order.client_order_id}")
                        raise
                    await self._update_order_fills_from_event_or_create(tracked_order, order_data)
                    order_update = OrderUpdate(
                        trading_pair=tracked_order.trading_pair,
                        update_timestamp=int(order_data["timestamp"]) * 1e-3,
                        new_state=CONSTANTS.ORDER_STATE[order_data["status"]],
                        client_order_id=client_order_id,
                        exchange_order_id=str(order_data["orderId"]),
                    )
                    self._order_tracker.process_order_update(order_update=order_update)

                elif event_type == "balance":
                    self._process_balance_message(event_message)

            except asyncio.CancelledError:
                raise
            except Exception:
                self.logger().error("Unexpected error in user stream listener loop.", exc_info=True)
                await asyncio.sleep(5.0)
Esempio n. 29
0
    def test_update_with_order_update_client_order_id_mismatch(self):
        order: InFlightOrder = InFlightOrder(
            client_order_id=self.client_order_id,
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            creation_timestamp=1640001112.0,
            price=Decimal("1.0"),
        )

        mismatch_order_update: OrderUpdate = OrderUpdate(
            client_order_id="mismatchClientOrderId",
            exchange_order_id="mismatchExchangeOrderId",
            trading_pair=self.trading_pair,
            update_timestamp=1,
            new_state=OrderState.OPEN,
        )

        self.assertFalse(order.update_with_order_update(mismatch_order_update))
        self.assertEqual(Decimal("0"), order.executed_amount_base)
        self.assertEqual(Decimal("0"), order.executed_amount_quote)
        self.assertEqual(order.creation_timestamp, order.last_update_timestamp)
    def test_update_exchange_id_with_order_update(self):
        order: InFlightOrder = InFlightOrder(
            client_order_id=self.client_order_id,
            trading_pair=self.trading_pair,
            order_type=OrderType.LIMIT,
            trade_type=TradeType.BUY,
            amount=Decimal("1000.0"),
            price=Decimal("1.0"),
        )

        order_update: OrderUpdate = OrderUpdate(
            client_order_id=self.client_order_id,
            exchange_order_id=self.exchange_order_id,
            trading_pair=self.trading_pair,
            update_timestamp=1,
            new_state=OrderState.OPEN,
        )

        result = order.update_with_order_update(order_update)
        self.assertTrue(result)
        self.assertEqual(self.exchange_order_id, order.exchange_order_id)
        self.assertTrue(order.exchange_order_id_update_event.is_set())
        self.assertEqual(0, len(order.order_fills))