Пример #1
0
 async def execute_cancel(self, trading_pair: str, client_order_id: str):
     try:
         params = {
             "origClientOrderId": client_order_id,
             "symbol": convert_to_exchange_trading_pair(trading_pair)
         }
         response = await self.request(
             path="/fapi/v1/order",
             params=params,
             method=MethodType.DELETE,
             is_signed=True,
             add_timestamp = True,
             return_err=True
         )
         if response.get("code") == -2011 or "Unknown order sent" in response.get("msg", ""):
             self.logger().debug(f"The order {client_order_id} does not exist on Binance Perpetuals. "
                                 f"No cancellation needed.")
             self.stop_tracking_order(client_order_id)
             self.trigger_event(self.MARKET_ORDER_CANCELLED_EVENT_TAG,
                                OrderCancelledEvent(self.current_timestamp, client_order_id))
             return {
                 "origClientOrderId": client_order_id
             }
     except Exception as e:
         self.logger().error(f"Could not cancel order {client_order_id} (on Binance Perp. {trading_pair})")
         raise e
     if response.get("status", None) == "CANCELED":
         self.logger().info(f"Successfully canceled order {client_order_id}")
         self.stop_tracking_order(client_order_id)
         self.trigger_event(self.MARKET_ORDER_CANCELLED_EVENT_TAG,
                            OrderCancelledEvent(self.current_timestamp, client_order_id))
     return response
Пример #2
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)
    def test_hanging_order_removed_when_cancelled(self):
        strategy_active_orders = []
        strategy_logs = []
        app_notifications = []

        type(self.strategy).active_orders = PropertyMock(return_value=strategy_active_orders)
        self.strategy.log_with_clock.side_effect = lambda log_type, message: strategy_logs.append((log_type, message))
        self.strategy.notify_hb_app.side_effect = lambda message: app_notifications.append(message)

        new_order = LimitOrder("Order-1234567890000000",
                               "BTC-USDT",
                               True,
                               "BTC",
                               "USDT",
                               Decimal(101),
                               Decimal(1))

        self.tracker.add_order(new_order)
        strategy_active_orders.append(new_order)

        self.tracker.update_strategy_orders_with_equivalent_orders()

        # Now we simulate the order is cancelled
        self.tracker._did_cancel_order(MarketEvent.OrderCancelled,
                                       self,
                                       OrderCancelledEvent(new_order.client_order_id, new_order.client_order_id))

        self.assertIn((logging.INFO, "(BTC-USDT) Hanging order Order-1234567890000000 cancelled."), strategy_logs)
        self.assertIn("(BTC-USDT) Hanging order Order-1234567890000000 cancelled.", app_notifications)
        self.assertTrue(len(self.tracker.strategy_current_hanging_orders) == 0)
        self.assertNotIn(new_order, self.tracker.original_orders)
Пример #4
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)
Пример #5
0
 async def _execute_cancel(self, o: DigifinexInFlightOrder) -> str:
     """
     Executes order cancellation process by first calling cancel-order API. The API result doesn't confirm whether
     the cancellation is successful, it simply states it receives the request.
     :param trading_pair: The market trading pair
     :param order_id: The internal order id
     order.last_state to change to CANCELED
     """
     try:
         await self._global.rest_api.request(
             "post", "spot/order/cancel", {"order_id": o.exchange_order_id},
             True)
         if o.client_order_id in self._in_flight_orders:
             self.trigger_event(
                 MarketEvent.OrderCancelled,
                 OrderCancelledEvent(self.current_timestamp,
                                     o.client_order_id))
             del self._in_flight_orders[o.client_order_id]
         return o.exchange_order_id
     except asyncio.CancelledError:
         raise
     except Exception as e:
         self.logger().network(
             f"Failed to cancel order {o.exchange_order_id}: {str(e)}",
             exc_info=True,
             app_warning_msg=
             f"Failed to cancel the order {o.exchange_order_id} on Digifinex. "
             f"Check API key and network connection.")
    def test_hanging_order_removed_when_cancelled(self):
        strategy_active_orders = []

        type(self.strategy).active_orders = PropertyMock(
            return_value=strategy_active_orders)

        new_order = LimitOrder("Order-1234567890000000",
                               "BTC-USDT",
                               True,
                               "BTC",
                               "USDT",
                               Decimal(101),
                               Decimal(1),
                               creation_timestamp=1234567890000000)

        self.tracker.add_order(new_order)
        strategy_active_orders.append(new_order)

        self.tracker.update_strategy_orders_with_equivalent_orders()

        # Now we simulate the order is cancelled
        self.tracker._did_cancel_order(
            MarketEvent.OrderCancelled.value, self,
            OrderCancelledEvent(datetime.now().timestamp(),
                                new_order.client_order_id,
                                new_order.client_order_id))

        self.assertTrue(
            self._is_logged(
                "INFO",
                "(BTC-USDT) Hanging order Order-1234567890000000 canceled."))
        self.assertTrue(len(self.tracker.strategy_current_hanging_orders) == 0)
        self.assertNotIn(new_order, self.tracker.original_orders)
Пример #7
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)
Пример #8
0
    async def cancel_all(self, timeout_seconds: float):
        """
        Cancels all in-flight orders and waits for cancellation results.
        Used by bot's top level stop and exit commands (cancelling outstanding orders on exit)
        :param timeout_seconds: The timeout at which the operation will be canceled.
        :returns List of CancellationResult which indicates whether each order is successfully cancelled.
        """
        cancellation_results = []
        try:
            await self._api_request(
                "delete",
                "cash/order/all",
                {},
                True,
                force_auth_path_url="order/all"
            )

            open_orders = await self.get_open_orders()

            for cl_order_id, tracked_order in self._in_flight_orders.copy().items():
                open_order = [o for o in open_orders if o.client_order_id == cl_order_id]
                if not open_order:
                    cancellation_results.append(CancellationResult(cl_order_id, True))
                    self.trigger_event(MarketEvent.OrderCancelled,
                                       OrderCancelledEvent(self.current_timestamp, cl_order_id))
                    self.stop_tracking_order(cl_order_id)
                else:
                    cancellation_results.append(CancellationResult(cl_order_id, False))
        except Exception:
            self.logger().network(
                "Failed to cancel all orders.",
                exc_info=True,
                app_warning_msg="Failed to cancel all orders on AscendEx. Check API key and network connection."
            )
        return cancellation_results
Пример #9
0
    async def cancel_all(self, timeout_sec: float) -> List[CancellationResult]:
        """
        Cancels all in-flight orders and waits for cancellation results.
        Used by bot's top level stop and exit commands (cancelling outstanding orders on exit)
        :param timeout_sec: The timeout at which the operation will be canceled.
        :returns List of CancellationResult which indicates whether each order is successfully cancelled.
        """

        # Note: NDAX's CancelOrder endpoint simply indicates if the cancel requests has been succesfully received.
        cancellation_results = []
        tracked_orders = self.in_flight_orders
        try:
            for order in tracked_orders.values():
                self.cancel(trading_pair=order.trading_pair,
                            order_id=order.client_order_id)

            open_orders = await self.get_open_orders()

            for client_oid, tracked_order in tracked_orders.items():
                matched_order = [o for o in open_orders if o.client_order_id == client_oid]
                if not matched_order:
                    cancellation_results.append(CancellationResult(client_oid, True))
                    self.trigger_event(MarketEvent.OrderCancelled,
                                       OrderCancelledEvent(self.current_timestamp, client_oid))
                else:
                    cancellation_results.append(CancellationResult(client_oid, False))

        except Exception as ex:
            self.logger().network(
                f"Failed to cancel all orders ({ex})",
                exc_info=True,
                app_warning_msg="Failed to cancel all orders on NDAX. Check API key and network connection."
            )
        return cancellation_results
Пример #10
0
    def test_remaining_quantity_updated_after_cancel_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.OrderCancelled,
            OrderCancelledEvent(self.market.current_timestamp,
                                bid_order.client_order_id))

        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 cancel (id: {bid_order.client_order_id})"
            ))
Пример #11
0
 def simulate_cancel_order(market_info: MarketTradingPairTuple, order: Union[LimitOrder, MarketOrder]):
     market_info.market.trigger_event(
         MarketEvent.OrderCancelled,
         OrderCancelledEvent(
             int(time.time() * 1e3),
             order.client_order_id if isinstance(order, LimitOrder) else order.order_id,
         )
     )
Пример #12
0
    async def _execute_cancel(self, trading_pair: str, order_id: str) -> CancellationResult:
        """
        Executes order cancellation process by first calling cancel-order API. The API result doesn't confirm whether
        the cancellation is successful, it simply states it receives the request.
        :param trading_pair: The market trading pair (Unused during cancel on AltMarkets.io)
        :param order_id: The internal order id
        order.last_state to change to CANCELED
        """
        order_state, errors_found = None, {}
        try:
            tracked_order = self._in_flight_orders.get(order_id)
            if tracked_order is None:
                self.logger().warning(f"Failed to cancel order {order_id}. Order not found in inflight orders.")
            elif not tracked_order.is_local:
                if tracked_order.exchange_order_id is None:
                    try:
                        async with timeout(6):
                            await tracked_order.get_exchange_order_id()
                    except Exception:
                        order_state = "reject"
                exchange_order_id = tracked_order.exchange_order_id
                response = await self._api_request("POST",
                                                   Constants.ENDPOINT["ORDER_DELETE"].format(id=exchange_order_id),
                                                   is_auth_required=True,
                                                   limit_id=Constants.RL_ID_ORDER_DELETE)
                if isinstance(response, dict):
                    order_state = response.get("state", None)
        except asyncio.CancelledError:
            raise
        except asyncio.TimeoutError:
            self.logger().info(f"The order {order_id} could not be cancelled due to a timeout."
                               " The action will be retried later.")
            errors_found = {"message": "Timeout during order cancellation"}
        except AltmarketsAPIError as e:
            errors_found = e.error_payload.get('errors', e.error_payload)
            if isinstance(errors_found, dict):
                order_state = errors_found.get("state", None)
            if order_state is None or 'market.order.invaild_id_or_uuid' in errors_found:
                self._order_not_found_records[order_id] = self._order_not_found_records.get(order_id, 0) + 1

        if order_state in Constants.ORDER_STATES['CANCEL_WAIT'] or \
                self._order_not_found_records.get(order_id, 0) >= self.ORDER_NOT_EXIST_CANCEL_COUNT:
            self.logger().info(f"Successfully cancelled order {order_id} on {Constants.EXCHANGE_NAME}.")
            self.stop_tracking_order(order_id)
            self.trigger_event(MarketEvent.OrderCancelled,
                               OrderCancelledEvent(self.current_timestamp, order_id))
            tracked_order.cancelled_event.set()
            return CancellationResult(order_id, True)
        else:
            if not tracked_order or not tracked_order.is_local:
                err_msg = errors_found.get('message', errors_found) if isinstance(errors_found, dict) else errors_found
                self.logger().network(
                    f"Failed to cancel order - {order_id}: {err_msg}",
                    exc_info=True,
                    app_warning_msg=f"Failed to cancel the order {order_id} on {Constants.EXCHANGE_NAME}. "
                                    f"Check API key and network connection."
                )
            return CancellationResult(order_id, False)
Пример #13
0
    async def _execute_cancel(self, trading_pair: str, order_id: str) -> str:
        """
        To determine if an order is successfully cancelled, we either call the
        GetOrderStatus/GetOpenOrders endpoint or wait for a OrderStateEvent/OrderTradeEvent from the WS.
        :param trading_pair: The market (e.g. BTC-CAD) the order is in.
        :param order_id: The client_order_id of the order to be cancelled.
        """
        try:
            tracked_order: Optional[
                NdaxInFlightOrder] = self._in_flight_orders.get(
                    order_id, None)
            if tracked_order is None:
                raise ValueError(
                    f"Failed to cancel order - {order_id}. Order not being tracked."
                )
            if tracked_order.is_locally_working:
                raise NdaxInFlightOrderNotCreated(
                    f"Failed to cancel order - {order_id}. Order not yet created."
                    f" This is most likely due to rate-limiting.")

            body_params = {
                "OMSId": 1,
                "AccountId": await self.initialized_account_id(),
                "OrderId": await tracked_order.get_exchange_order_id()
            }

            # The API response simply verifies that the API request have been received by the API servers.
            await self._api_request(method="POST",
                                    path_url=CONSTANTS.CANCEL_ORDER_PATH_URL,
                                    data=body_params,
                                    is_auth_required=True)

            return order_id

        except asyncio.CancelledError:
            raise
        except NdaxInFlightOrderNotCreated:
            raise
        except Exception as e:
            self.logger().error(f"Failed to cancel order {order_id}: {str(e)}")
            self.logger().network(
                f"Failed to cancel order {order_id}: {str(e)}",
                exc_info=True,
                app_warning_msg=f"Failed to cancel order {order_id} on NDAX. "
                f"Check API key and network connection.")
            if RESOURCE_NOT_FOUND_ERR in str(e):
                self._order_not_found_records[
                    order_id] = self._order_not_found_records.get(order_id,
                                                                  0) + 1
                if self._order_not_found_records[
                        order_id] >= self.ORDER_EXCEED_NOT_FOUND_COUNT:
                    self.logger().warning(
                        f"Order {order_id} does not seem to be active, will stop tracking order..."
                    )
                    self.stop_tracking_order(order_id)
                    self.trigger_event(
                        MarketEvent.OrderCancelled,
                        OrderCancelledEvent(self.current_timestamp, order_id))
Пример #14
0
 async def _execute_cancel(self, trading_pair: str, order_id: str) -> str:
     """
     Executes order cancellation process by first calling cancel-order API. The API result doesn't confirm whether
     the cancellation is successful, it simply states it receives the request.
     :param trading_pair: The market trading pair (Unused during cancel on Gate.io)
     :param order_id: The internal order id
     order.last_state to change to CANCELED
     """
     order_was_cancelled = False
     try:
         tracked_order = self._in_flight_orders.get(order_id)
         if tracked_order is None:
             raise ValueError(
                 f"Failed to cancel order - {order_id}. Order not found.")
         if tracked_order.exchange_order_id is None:
             await tracked_order.get_exchange_order_id()
         ex_order_id = tracked_order.exchange_order_id
         await self._api_request(
             "DELETE",
             Constants.ENDPOINT["ORDER_DELETE"].format(id=ex_order_id),
             params={
                 'currency_pair':
                 convert_to_exchange_trading_pair(trading_pair)
             },
             is_auth_required=True)
         order_was_cancelled = True
     except asyncio.CancelledError:
         raise
     except (asyncio.TimeoutError, GateIoAPIError) as e:
         if isinstance(e, asyncio.TimeoutError):
             err_msg = 'Order not tracked.'
             err_lbl = 'ORDER_NOT_FOUND'
         else:
             err_msg = e.error_message
             err_lbl = e.error_label
         self._order_not_found_records[
             order_id] = self._order_not_found_records.get(order_id, 0) + 1
         if err_lbl == 'ORDER_NOT_FOUND' and \
                 self._order_not_found_records[order_id] >= self.ORDER_NOT_EXIST_CANCEL_COUNT:
             order_was_cancelled = True
     if order_was_cancelled:
         self.logger().info(
             f"Successfully cancelled order {order_id} on {Constants.EXCHANGE_NAME}."
         )
         self.stop_tracking_order(order_id)
         self.trigger_event(
             MarketEvent.OrderCancelled,
             OrderCancelledEvent(self.current_timestamp, order_id))
         tracked_order.cancelled_event.set()
         return CancellationResult(order_id, True)
     else:
         self.logger().network(
             f"Failed to cancel order {order_id}: {err_msg}",
             exc_info=True,
             app_warning_msg=
             f"Failed to cancel the order {order_id} on {Constants.EXCHANGE_NAME}. "
             f"Check API key and network connection.")
         return CancellationResult(order_id, False)
Пример #15
0
    async def cancel_all(self, timeout_seconds: float):
        """
        Cancels all in-flight orders and waits for cancellation results.
        Used by bot's top level stop and exit commands (cancelling outstanding orders on exit)
        :param timeout_seconds: The timeout at which the operation will be canceled.
        :returns List of CancellationResult which indicates whether each order is successfully cancelled.
        """
        if self._trading_pairs is None:
            raise Exception(
                "cancel_all can only be used when trading_pairs are specified."
            )
        tracked_orders: Dict[
            str,
            CryptoComInFlightOrder] = self._in_flight_orders.copy().items()
        cancellation_results = []
        try:
            tasks = []

            for _, order in tracked_orders:
                api_params = {
                    "instrument_name":
                    crypto_com_utils.convert_to_exchange_trading_pair(
                        order.trading_pair),
                    "order_id":
                    order.exchange_order_id,
                }
                tasks.append(
                    self._api_request(method="post",
                                      path_url=CONSTANTS.CANCEL_ORDER_PATH_URL,
                                      params=api_params,
                                      is_auth_required=True))

            await safe_gather(*tasks)

            open_orders = await self.get_open_orders()
            for cl_order_id, tracked_order in tracked_orders:
                open_order = [
                    o for o in open_orders if o.client_order_id == cl_order_id
                ]
                if not open_order:
                    cancellation_results.append(
                        CancellationResult(cl_order_id, True))
                    self.trigger_event(
                        MarketEvent.OrderCancelled,
                        OrderCancelledEvent(self.current_timestamp,
                                            cl_order_id))
                    self.stop_tracking_order(cl_order_id)
                else:
                    cancellation_results.append(
                        CancellationResult(cl_order_id, False))
        except Exception:
            self.logger().network(
                "Failed to cancel all orders.",
                exc_info=True,
                app_warning_msg=
                "Failed to cancel all orders on Crypto.com. Check API key and network connection."
            )
        return cancellation_results
Пример #16
0
 def _trigger_cancelled_event(self, order: InFlightOrder):
     self._connector.trigger_event(
         MarketEvent.OrderCancelled,
         OrderCancelledEvent(
             timestamp=self.current_timestamp,
             order_id=order.client_order_id,
             exchange_order_id=order.exchange_order_id,
         ),
     )
Пример #17
0
    async def cancel_all(self, timeout_seconds: float):
        """
        Cancels all in-flight orders and waits for cancellation results.
        Used by bot's top level stop and exit commands (cancelling outstanding orders on exit)
        :param timeout_seconds: The timeout at which the operation will be canceled.
        :returns List of CancellationResult which indicates whether each order is successfully cancelled.
        """
        cancellation_results = []
        try:
            tracked_orders: Dict[
                str, AscendExInFlightOrder] = self._in_flight_orders.copy()

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

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

            open_orders = await self.get_open_orders()

            for cl_order_id, tracked_order in tracked_orders.items():
                open_order = [
                    o for o in open_orders if o.client_order_id == cl_order_id
                ]
                if not open_order:
                    cancellation_results.append(
                        CancellationResult(cl_order_id, True))
                    self.trigger_event(
                        MarketEvent.OrderCancelled,
                        OrderCancelledEvent(self.current_timestamp,
                                            cl_order_id))
                    self.stop_tracking_order(cl_order_id)
                else:
                    cancellation_results.append(
                        CancellationResult(cl_order_id, False))
        except Exception:
            self.logger().network(
                "Failed to cancel all orders.",
                exc_info=True,
                app_warning_msg=
                "Failed to cancel all orders on AscendEx. Check API key and network connection."
            )
        return cancellation_results
    def test_renew_hanging_orders_past_max_order_age(self):
        cancelled_orders_ids = []
        strategy_active_orders = []
        type(self.strategy).current_timestamp = PropertyMock(
            return_value=1234967891)
        type(self.strategy).active_orders = PropertyMock(
            return_value=strategy_active_orders)
        self.strategy.cancel_order.side_effect = lambda order_id: cancelled_orders_ids.append(
            order_id)
        self.strategy.buy_with_specific_market.return_value = "Order-1234569990000000"

        # Order just executed
        new_order = LimitOrder("Order-1234567890000000", "BTC-USDT", True,
                               "BTC", "USDT", Decimal(101), Decimal(1))
        # Order executed 1900 seconds ago
        old_order = LimitOrder("Order-1234565991000000", "BTC-USDT", True,
                               "BTC", "USDT", Decimal(105), Decimal(1))

        self.tracker.add_order(new_order)
        strategy_active_orders.append(new_order)
        self.tracker.add_order(old_order)
        strategy_active_orders.append(old_order)

        self.tracker.update_strategy_orders_with_equivalent_orders()

        self.assertTrue(
            any(order.trading_pair == "BTC-USDT"
                and order.price == Decimal(105)
                for order in self.tracker.strategy_current_hanging_orders))

        # When calling the renew logic, the old order should start the renew process (it should be canceled)
        # but it will only stop being a current hanging order once the cancel confirmation arrives
        self.tracker.process_tick()
        self.assertTrue(old_order.client_order_id in cancelled_orders_ids)
        self.assertTrue(
            any(order.trading_pair == "BTC-USDT"
                and order.price == Decimal(105)
                for order in self.tracker.strategy_current_hanging_orders))

        # When the cancel is confirmed the order should no longer be considered a hanging order
        strategy_active_orders.remove(old_order)
        self.tracker._did_cancel_order(
            MarketEvent.OrderCancelled, self,
            OrderCancelledEvent(old_order.client_order_id,
                                old_order.client_order_id))
        self.assertTrue(
            self._is_logged(
                "INFO",
                f"(BTC-USDT) Hanging order {old_order.client_order_id} "
                f"has been cancelled as part of the renew process. "
                f"Now the replacing order will be created."))
        self.assertFalse(
            any(order.order_id == old_order.client_order_id
                for order in self.tracker.strategy_current_hanging_orders))
        self.assertTrue(
            any(order.order_id == "Order-1234569990000000"
                for order in self.tracker.strategy_current_hanging_orders))
Пример #19
0
    async def _execute_cancel(self, trading_pair: str, order_id: str) -> CancellationResult:
        """
        Executes order cancellation process by first calling cancel-order API. The API result doesn't confirm whether
        the cancellation is successful, it simply states it receives the request.
        :param trading_pair: The market trading pair (Unused during cancel on CoinZoom)
        :param order_id: The internal order id
        order.last_state to change to CANCELED
        """
        order_was_cancelled = False
        try:
            tracked_order = self._in_flight_orders.get(order_id)
            if tracked_order is None:
                raise ValueError(f"Failed to cancel order - {order_id}. Order not found.")

            if not tracked_order.is_local:
                if tracked_order.exchange_order_id is None:
                    await tracked_order.get_exchange_order_id()
                ex_order_id = tracked_order.exchange_order_id
                api_params = {
                    "orderId": ex_order_id,
                    "symbol": convert_to_exchange_trading_pair(trading_pair)
                }
                await self._api_request("POST",
                                        Constants.ENDPOINT["ORDER_DELETE"],
                                        api_params,
                                        is_auth_required=True)
                order_was_cancelled = True
        except asyncio.CancelledError:
            raise
        except asyncio.TimeoutError:
            self.logger().info(f"The order {order_id} could not be cancelled due to a timeout."
                               " The action will be retried later.")
            err = {"message": "Timeout during order cancellation"}
        except CoinzoomAPIError as e:
            err = e.error_payload.get('error', e.error_payload)
            self.logger().error(f"Order Cancel API Error: {err}")
            # CoinZoom doesn't report any error if the order wasn't found so we can only handle API failures here.
            self._order_not_found_records[order_id] = self._order_not_found_records.get(order_id, 0) + 1
            if self._order_not_found_records[order_id] >= self.ORDER_NOT_EXIST_CANCEL_COUNT:
                order_was_cancelled = True

        if order_was_cancelled:
            self.logger().info(f"Successfully cancelled order {order_id} on {Constants.EXCHANGE_NAME}.")
            self.stop_tracking_order(order_id)
            self.trigger_event(MarketEvent.OrderCancelled,
                               OrderCancelledEvent(self.current_timestamp, order_id))
            tracked_order.cancelled_event.set()
            return CancellationResult(order_id, True)
        else:
            if not tracked_order.is_local:
                self.logger().network(
                    f"Failed to cancel order {order_id}: {err.get('message', str(err))}",
                    exc_info=True,
                    app_warning_msg=f"Failed to cancel the order {order_id} on {Constants.EXCHANGE_NAME}. "
                                    f"Check API key and network connection."
                )
            return CancellationResult(order_id, False)
Пример #20
0
    async def cancel_all(self, timeout_seconds: float) -> List[CancellationResult]:
        orders_by_trading_pair = {}

        for order in self._in_flight_orders.values():
            orders_by_trading_pair[order.trading_pair] = orders_by_trading_pair.get(order.trading_pair, [])
            orders_by_trading_pair[order.trading_pair].append(order)

        if len(orders_by_trading_pair) == 0:
            return []

        for trading_pair in orders_by_trading_pair:
            cancel_order_ids = [o.exchange_order_id for o in orders_by_trading_pair[trading_pair]]
            is_need_loop = True
            while is_need_loop:
                if len(cancel_order_ids) > self.ORDER_LEN_LIMIT:
                    is_need_loop = True
                    this_turn_cancel_order_ids = cancel_order_ids[:self.ORDER_LEN_LIMIT]
                    cancel_order_ids = cancel_order_ids[self.ORDER_LEN_LIMIT:]
                else:
                    this_turn_cancel_order_ids = cancel_order_ids
                    is_need_loop = False
                self.logger().debug(
                    f"cancel_order_ids {this_turn_cancel_order_ids} orders_by_trading_pair[trading_pair]")
                params = {
                    'order_ids': quote(','.join([o for o in this_turn_cancel_order_ids])),
                }

                cancellation_results = []
                try:
                    cancel_all_results = await self._api_request(
                        "DELETE",
                        path_url=CONSTANTS.MEXC_ORDER_CANCEL,
                        params=params,
                        is_auth_required=True
                    )

                    for order_result_client_order_id, order_result_value in cancel_all_results['data'].items():
                        for o in orders_by_trading_pair[trading_pair]:
                            if o.client_order_id == order_result_client_order_id:
                                result_bool = True if order_result_value == "invalid order state" or order_result_value == "success" else False
                                cancellation_results.append(CancellationResult(o.client_order_id, result_bool))
                                if result_bool:
                                    self.trigger_event(self.MARKET_ORDER_CANCELLED_EVENT_TAG,
                                                       OrderCancelledEvent(self.current_timestamp,
                                                                           order_id=o.client_order_id,
                                                                           exchange_order_id=o.exchange_order_id))
                                    self.stop_tracking_order(o.client_order_id)

                except Exception as ex:

                    self.logger().network(
                        f"Failed to cancel all orders: {this_turn_cancel_order_ids}" + repr(ex),
                        exc_info=True,
                        app_warning_msg="Failed to cancel all orders on Mexc. Check API key and network connection."
                    )
        return cancellation_results
Пример #21
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(*[
                GatewayHttpClient.get_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.last_state == "CANCELING":
                        if self.is_amm_order(tracked_order):
                            self.trigger_event(
                                MarketEvent.OrderCancelled,
                                OrderCancelledEvent(
                                    self.current_timestamp,
                                    tracked_order.client_order_id,
                                    tracked_order.exchange_order_id,
                                ))
                            self.logger().info(
                                f"The {tracked_order.trade_type.name} order "
                                f"{tracked_order.client_order_id} has been canceled "
                                f"according to the order status API.")
                        elif self.is_approval_order(tracked_order):
                            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.")
                        tracked_order.last_state = "CANCELED"
                    self.stop_tracking_order(tracked_order.client_order_id)
Пример #22
0
    async def cancel_all(self, timeout_seconds: float):
        """
        Cancels all in-flight orders and waits for cancellation results.
        Used by bot's top level stop and exit commands (cancelling outstanding orders on exit)
        :param timeout_seconds: The timeout at which the operation will be canceled.
        :returns List of CancellationResult which indicates whether each order is successfully cancelled.
        """
        if self._trading_pairs is None:
            raise Exception(
                "cancel_all can only be used when trading_pairs are specified."
            )
        cancellation_results = []

        for trading_pair in self._trading_pairs:
            order_ids = [
                await o.get_exchange_order_id()
                for _, o in self.in_flight_orders.items()
                if o.trading_pair == trading_pair
            ]

            api_params = {
                "symbol":
                k2_utils.convert_to_exchange_trading_pair(trading_pair),
                "orderids": ",".join(order_ids)
            }

            try:
                await self._api_request(method="POST",
                                        path_url=constants.CANCEL_ALL_ORDERS,
                                        params=api_params,
                                        is_auth_required=True)
                open_orders = await self.get_open_orders()
                for cl_order_id, tracked_order in self._in_flight_orders.items(
                ):
                    open_order = [
                        o for o in open_orders
                        if o.client_order_id == cl_order_id
                    ]
                    if not open_order:
                        cancellation_results.append(
                            CancellationResult(cl_order_id, True))
                        self.trigger_event(
                            MarketEvent.OrderCancelled,
                            OrderCancelledEvent(self.current_timestamp,
                                                cl_order_id))
                    else:
                        cancellation_results.append(
                            CancellationResult(cl_order_id, False))
            except Exception:
                self.logger().network(
                    "Failed to cancel all orders.",
                    exc_info=True,
                    app_warning_msg=
                    "Failed to cancel all orders on K2. Check API key and network connection."
                )
        return cancellation_results
Пример #23
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)
Пример #24
0
    async def cancel_all(self, timeout_seconds: float):
        """
        Cancels all in-flight orders and waits for cancellation results.
        Used by bot's top level stop and exit commands (cancelling outstanding orders on exit)
        :param timeout_seconds: The timeout at which the operation will be canceled.
        :returns List of CancellationResult which indicates whether each order is successfully cancelled.
        """
        if self._trading_pairs is None:
            raise Exception(
                "cancel_all can only be used when trading_pairs are specified."
            )
        cancellation_results = []
        try:

            # ProBit does not have cancel_all_order endpoint
            tasks = []
            for tracked_order in self.in_flight_orders.values():
                body_params = {
                    "market_id": tracked_order.trading_pair,
                    "order_id": tracked_order.exchange_order_id
                }
                tasks.append(
                    self._api_request(method="POST",
                                      path_url=CONSTANTS.CANCEL_ORDER_URL,
                                      data=body_params,
                                      is_auth_required=True))

            await safe_gather(*tasks)

            open_orders = await self.get_open_orders()
            for cl_order_id, tracked_order in self._in_flight_orders.items():
                open_order = [
                    o for o in open_orders if o.client_order_id == cl_order_id
                ]
                if not open_order:
                    cancellation_results.append(
                        CancellationResult(cl_order_id, True))
                    self.trigger_event(
                        MarketEvent.OrderCancelled,
                        OrderCancelledEvent(self.current_timestamp,
                                            cl_order_id))
                else:
                    cancellation_results.append(
                        CancellationResult(cl_order_id, False))
        except Exception:
            self.logger().network(
                "Failed to cancel all orders.",
                exc_info=True,
                app_warning_msg=
                "Failed to cancel all orders on ProBit. Check API key and network connection."
            )
        return cancellation_results
    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 cancel_order(self, client_order_id: str):
        in_flight_order = self._in_flight_orders.get(client_order_id)
        cancellation_event = OrderCancelledEvent(now(), client_order_id)
        exchange_order_id = in_flight_order.exchange_order_id

        if in_flight_order is None:
            self.logger().warning(
                "Cancelled an untracked order {client_order_id}")
            self.trigger_event(ORDER_CANCELLED_EVENT, cancellation_event)
            return False

        try:
            if exchange_order_id is None:
                # Note, we have no way of canceling an order or querying for information about the order
                # without an exchange_order_id
                if in_flight_order.creation_timestamp < (
                        self.time_now_s() - UNRECOGNIZED_ORDER_DEBOUCE):
                    # We'll just have to assume that this order doesn't exist
                    self.stop_tracking_order(in_flight_order.client_order_id)
                    self.trigger_event(ORDER_CANCELLED_EVENT,
                                       cancellation_event)
                    return False
            params = {"clOrdID": client_order_id}
            await self._api_request(path=CONSTANTS.ORDER_URL,
                                    is_auth_required=True,
                                    params=params,
                                    method=RESTMethod.DELETE)
            return True

        except Exception as e:
            if "Not Found" in str(e):
                if in_flight_order.creation_timestamp < (
                        self.time_now_s() - UNRECOGNIZED_ORDER_DEBOUCE):
                    # Order didn't exist on exchange, mark this as canceled
                    self.stop_tracking_order(in_flight_order.client_order_id)
                    self.trigger_event(ORDER_CANCELLED_EVENT,
                                       cancellation_event)
                    return False
                else:
                    raise Exception(
                        f"order {client_order_id} does not yet exist on the exchange and could not be cancelled."
                    )
        except Exception as e:
            self.logger().warning(f"Failed to cancel order {client_order_id}")
            self.logger().info(e)
            return False
Пример #27
0
 def _process_order_status(self, exchange_order_id: str, status: int):
     """
     Updates in-flight order and triggers cancellation or failure event if needed.
     """
     tracked_order = self.find_exchange_order(exchange_order_id)
     if tracked_order is None:
         return
     client_order_id = tracked_order.client_order_id
     # Update order execution status
     tracked_order.last_state = str(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)
Пример #28
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)
Пример #29
0
 async def cancel_all(self, timeout_seconds: float):
     """
     Cancels all in-flight orders and waits for cancellation results.
     Used by bot's top level stop and exit commands (cancelling outstanding orders on exit)
     :param timeout_seconds: The timeout at which the operation will be canceled.
     :returns List of CancellationResult which indicates whether each order is successfully cancelled.
     """
     if self._trading_pairs is None:
         raise Exception(
             "cancel_all can only be used when trading_pairs are specified."
         )
     cancellation_results = []
     try:
         for trading_pair in self._trading_pairs:
             await self._api_request(
                 "post", "private/cancel-all-orders", {
                     "instrument_name":
                     crypto_com_utils.convert_to_exchange_trading_pair(
                         trading_pair)
                 }, True)
         open_orders = await self.get_open_orders()
         for cl_order_id, tracked_order in self._in_flight_orders.items():
             open_order = [
                 o for o in open_orders if o.client_order_id == cl_order_id
             ]
             if not open_order:
                 cancellation_results.append(
                     CancellationResult(cl_order_id, True))
                 self.trigger_event(
                     MarketEvent.OrderCancelled,
                     OrderCancelledEvent(self.current_timestamp,
                                         cl_order_id))
             else:
                 cancellation_results.append(
                     CancellationResult(cl_order_id, False))
     except Exception:
         self.logger().network(
             "Failed to cancel all orders.",
             exc_info=True,
             app_warning_msg=
             "Failed to cancel all orders on Crypto.com. Check API key and network connection."
         )
     return cancellation_results
Пример #30
0
    def _process_order_message(self, order_msg: Dict[str, Any]):
        """
        Updates in-flight order and triggers trade, 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_order_id"]
        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"]

        # NOTE: In ProBit partially-filled orders will retain "filled" status when canceled.
        if tracked_order.is_cancelled or Decimal(str(order_msg["cancelled_quantity"])) > Decimal("0"):
            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)