示例#1
0
    def _did_create_order(self, event_tag: int, market: ConnectorBase,
                          evt: Union[BuyOrderCreatedEvent,
                                     SellOrderCreatedEvent]):
        if threading.current_thread() != threading.main_thread():
            self._ev_loop.call_soon_threadsafe(self._did_create_order,
                                               event_tag, market, evt)
            return

        session: Session = self.session
        base_asset, quote_asset = evt.trading_pair.split("-")
        timestamp: int = self.db_timestamp
        event_type: MarketEvent = self.market_event_tag_map[event_tag]
        order_record: Order = Order(
            id=evt.order_id,
            config_file_path=self._config_file_path,
            strategy=self._strategy_name,
            market=market.display_name,
            symbol=evt.trading_pair,
            base_asset=base_asset,
            quote_asset=quote_asset,
            creation_timestamp=timestamp,
            order_type=evt.type.name,
            amount=float(evt.amount),
            leverage=evt.leverage if evt.leverage else 1,
            price=float(evt.price) if evt.price == evt.price else 0,
            position=evt.position if evt.position else "NILL",
            last_status=event_type.name,
            last_update_timestamp=timestamp,
            exchange_order_id=evt.exchange_order_id)
        order_status: OrderStatus = OrderStatus(order=order_record,
                                                timestamp=timestamp,
                                                status=event_type.name)
        session.add(order_record)
        session.add(order_status)
        market.add_exchange_order_ids_from_market_recorder(
            {evt.exchange_order_id: evt.order_id})
        self.save_market_states(self._config_file_path, market, no_commit=True)
        session.commit()
示例#2
0
    def _did_create_order(self,
                          event_tag: int,
                          market: ConnectorBase,
                          evt: Union[BuyOrderCreatedEvent, SellOrderCreatedEvent]):
        if threading.current_thread() != threading.main_thread():
            self._ev_loop.call_soon_threadsafe(self._did_create_order, event_tag, market, evt)
            return

        base_asset, quote_asset = evt.trading_pair.split("-")
        timestamp = int(evt.creation_timestamp * 1e3)
        event_type: MarketEvent = self.market_event_tag_map[event_tag]

        with self._sql_manager.get_new_session() as session:
            with session.begin():
                order_record: Order = Order(id=evt.order_id,
                                            config_file_path=self._config_file_path,
                                            strategy=self._strategy_name,
                                            market=market.display_name,
                                            symbol=evt.trading_pair,
                                            base_asset=base_asset,
                                            quote_asset=quote_asset,
                                            creation_timestamp=timestamp,
                                            order_type=evt.type.name,
                                            amount=Decimal(evt.amount),
                                            leverage=evt.leverage if evt.leverage else 1,
                                            price=Decimal(evt.price) if evt.price == evt.price else Decimal(0),
                                            position=evt.position if evt.position else PositionAction.NIL.value,
                                            last_status=event_type.name,
                                            last_update_timestamp=timestamp,
                                            exchange_order_id=evt.exchange_order_id)
                order_status: OrderStatus = OrderStatus(order=order_record,
                                                        timestamp=timestamp,
                                                        status=event_type.name)
                session.add(order_record)
                session.add(order_status)
                market.add_exchange_order_ids_from_market_recorder({evt.exchange_order_id: evt.order_id})
                self.save_market_states(self._config_file_path, market, session=session)
    def _did_fill_order(self, event_tag: int, market: ConnectorBase,
                        evt: OrderFilledEvent):
        if threading.current_thread() != threading.main_thread():
            self._ev_loop.call_soon_threadsafe(self._did_fill_order, event_tag,
                                               market, evt)
            return

        base_asset, quote_asset = evt.trading_pair.split("-")
        timestamp: int = int(
            evt.timestamp *
            1e3) if evt.timestamp is not None else self.db_timestamp
        event_type: MarketEvent = self.market_event_tag_map[event_tag]
        order_id: str = evt.order_id

        with self._sql_manager.get_new_session() as session:
            with session.begin():
                # Try to find the order record, and update it if necessary.
                order_record: Optional[Order] = session.query(Order).filter(
                    Order.id == order_id).one_or_none()
                if order_record is not None:
                    order_record.last_status = event_type.name
                    order_record.last_update_timestamp = timestamp

                # Order status and trade fill record should be added even if the order record is not found, because it's
                # possible for fill event to come in before the order created event for market orders.
                order_status: OrderStatus = OrderStatus(order_id=order_id,
                                                        timestamp=timestamp,
                                                        status=event_type.name)

                trade_fill_record: TradeFill = TradeFill(
                    config_file_path=self.config_file_path,
                    strategy=self.strategy_name,
                    market=market.display_name,
                    symbol=evt.trading_pair,
                    base_asset=base_asset,
                    quote_asset=quote_asset,
                    timestamp=timestamp,
                    order_id=order_id,
                    trade_type=evt.trade_type.name,
                    order_type=evt.order_type.name,
                    price=Decimal(evt.price)
                    if evt.price == evt.price else Decimal(0),
                    amount=Decimal(evt.amount),
                    leverage=evt.leverage if evt.leverage else 1,
                    trade_fee=evt.trade_fee.to_json(),
                    exchange_trade_id=evt.exchange_trade_id,
                    position=evt.position
                    if evt.position else PositionAction.NIL.value,
                )
                session.add(order_status)
                session.add(trade_fill_record)
                self.save_market_states(self._config_file_path,
                                        market,
                                        session=session)

                market.add_trade_fills_from_market_recorder({
                    TradeFillOrderDetails(trade_fill_record.market,
                                          trade_fill_record.exchange_trade_id,
                                          trade_fill_record.symbol)
                })
                self.append_to_csv(trade_fill_record)
示例#4
0
    def restore_market_states(self, config_file_path: str, market: ConnectorBase):
        market_states: Optional[MarketState] = self.get_market_states(config_file_path, market)

        if market_states is not None:
            market.restore_tracking_states(market_states.saved_state)
class ClientOrderTrackerUnitTest(unittest.TestCase):
    # logging.Level required to receive logs from the exchange
    level = 0

    @classmethod
    def setUpClass(cls) -> None:
        super().setUpClass()
        cls.ev_loop = asyncio.get_event_loop()

        cls.base_asset = "COINALPHA"
        cls.quote_asset = "HBOT"
        cls.trading_pair = f"{cls.base_asset}-{cls.quote_asset}"
        cls.trade_fee_percent = Decimal("0.001")

    def setUp(self) -> None:
        super().setUp()
        self.log_records = []

        self.connector = ConnectorBase()
        self.connector._set_current_timestamp(1640000000.0)
        self.tracker = ClientOrderTracker(connector=self.connector)

        self.tracker.logger().setLevel(1)
        self.tracker.logger().addHandler(self)

    def handle(self, record):
        self.log_records.append(record)

    def _is_logged(self, log_level: str, message: str) -> bool:
        return any(
            record.levelname == log_level and record.getMessage() == message
            for record in self.log_records)

    def test_start_tracking_order(self):
        self.assertEqual(0, len(self.tracker.active_orders))

        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)

        self.assertEqual(1, len(self.tracker.active_orders))

    def test_stop_tracking_order(self):
        self.assertEqual(0, len(self.tracker.active_orders))

        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)
        self.assertEqual(1, len(self.tracker.active_orders))

        self.tracker.stop_tracking_order(order.client_order_id)

        self.assertEqual(0, len(self.tracker.active_orders))
        self.assertEqual(1, len(self.tracker.cached_orders))

    def test_cached_order_max_cache_size(self):
        for i in range(ClientOrderTracker.MAX_CACHE_SIZE + 1):
            order: InFlightOrder = InFlightOrder(
                client_order_id=f"someClientOrderId_{i}",
                trading_pair=self.trading_pair,
                order_type=OrderType.LIMIT,
                trade_type=TradeType.BUY,
                amount=Decimal("1000.0"),
                price=Decimal("1.0"),
            )
            self.tracker._cached_orders[order.client_order_id] = order

        self.assertEqual(ClientOrderTracker.MAX_CACHE_SIZE,
                         len(self.tracker.cached_orders))

        # First entry gets removed when the no. of cached order exceeds MAX_CACHE_SIZE
        self.assertNotIn("someClientOrderId_0", self.tracker._cached_orders)

    def test_cached_order_ttl_not_exceeded(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._cached_orders[order.client_order_id] = order

        self.assertIn(order.client_order_id, self.tracker._cached_orders)

    @patch(
        "hummingbot.connector.client_order_tracker.ClientOrderTracker.CACHED_ORDER_TTL",
        0.1)
    def test_cached_order_ttl_exceeded(self):
        tracker = ClientOrderTracker(self.connector)
        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"),
        )
        tracker._cached_orders[order.client_order_id] = order

        self.ev_loop.run_until_complete(asyncio.sleep(0.2))

        self.assertNotIn(order.client_order_id, tracker.cached_orders)

    def test_fetch_tracked_order_not_found(self):
        self.assertIsNone(
            self.tracker.fetch_tracked_order("someNonExistantOrderId"))

    def test_fetch_tracked_order(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)
        self.assertEqual(1, len(self.tracker.active_orders))

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

        self.assertTrue(fetched_order == order)

    def test_fetch_cached_order_not_found(self):
        self.assertIsNone(
            self.tracker.fetch_cached_order("someNonExistantOrderId"))

    def test_fetch_cached_order(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._cached_orders[order.client_order_id] = order
        self.assertEqual(1, len(self.tracker.cached_orders))

        fetched_order: InFlightOrder = self.tracker.fetch_cached_order(
            order.client_order_id)

        self.assertTrue(fetched_order == order)

    def test_fetch_order_by_client_order_id(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)
        self.assertEqual(1, len(self.tracker.active_orders))

        fetched_order: InFlightOrder = self.tracker.fetch_order(
            order.client_order_id)

        self.assertTrue(fetched_order == order)

    def test_fetch_order_by_exchange_order_id(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"),
            price=Decimal("1.0"),
        )
        self.tracker.start_tracking_order(order)
        self.assertEqual(1, len(self.tracker.active_orders))

        fetched_order: InFlightOrder = self.tracker.fetch_order(
            exchange_order_id=order.exchange_order_id)

        self.assertTrue(fetched_order == order)

    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})",
            ))

    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)

    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"),
            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,
        )

        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)

    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"),
            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,
        )

        self.tracker.process_order_update(order_cancelled_update)

        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.connector.event_logs))

        event_triggered = self.connector.event_logs[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)

    def test_process_order_update_trigger_order_failure_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"),
            price=Decimal("1.0"),
            initial_state=OrderState.OPEN,
        )

        self.tracker.start_tracking_order(order)

        order_failure_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.FAILED,
        )

        self.tracker.process_order_update(order_failure_update)

        self.assertTrue(
            self._is_logged(
                "INFO",
                f"Order {order.client_order_id} has failed. Order Update: {order_failure_update}"
            ))
        self.assertEqual(0, len(self.tracker.active_orders))
        self.assertEqual(1, len(self.tracker.cached_orders))
        self.assertEqual(1, len(self.connector.event_logs))

        event_triggered = self.connector.event_logs[0]
        self.assertIsInstance(event_triggered, MarketOrderFailureEvent)
        self.assertEqual(event_triggered.order_id, order.client_order_id)
        self.assertEqual(event_triggered.order_type, order.order_type)

    def test_process_order_update_trigger_filled_and_completed_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"),
            price=Decimal("1.0"),
            initial_state=OrderState.OPEN,
        )
        self.tracker.start_tracking_order(order)

        initial_order_filled_amount = order.amount / Decimal("2.0")
        initial_fee_paid = self.trade_fee_percent * initial_order_filled_amount
        order_fill_update_1: OrderUpdate = OrderUpdate(
            client_order_id=order.client_order_id,
            exchange_order_id="someExchangeOrderId",
            trading_pair=self.trading_pair,
            update_timestamp=1,
            new_state=OrderState.PARTIALLY_FILLED,
            trade_id=1,
            fill_price=order.price,
            executed_amount_base=initial_order_filled_amount,
            executed_amount_quote=order.price * initial_order_filled_amount,
            fee_asset=self.base_asset,
            cumulative_fee_paid=initial_fee_paid,
        )

        self.tracker.process_order_update(order_fill_update_1)

        # Check order update has been successfully applied
        updated_order: InFlightOrder = self.tracker.fetch_tracked_order(
            order.client_order_id)
        self.assertEqual(updated_order.exchange_order_id,
                         order_fill_update_1.exchange_order_id)
        self.assertTrue(updated_order.exchange_order_id_update_event.is_set())
        self.assertEqual(updated_order.current_state,
                         order_fill_update_1.new_state)
        self.assertTrue(updated_order.is_open)

        subsequent_order_filled_amount = order.amount - initial_order_filled_amount
        subsequent_fee_paid = self.trade_fee_percent * subsequent_order_filled_amount
        order_fill_update_2: OrderUpdate = OrderUpdate(
            client_order_id=order.client_order_id,
            exchange_order_id="someExchangeOrderId",
            trading_pair=self.trading_pair,
            update_timestamp=2,
            new_state=OrderState.FILLED,
            trade_id=2,
            fill_price=order.price,
            executed_amount_base=initial_order_filled_amount +
            subsequent_order_filled_amount,
            executed_amount_quote=order.price * order.amount,
            fee_asset=self.base_asset,
            cumulative_fee_paid=initial_fee_paid + subsequent_fee_paid,
        )

        self.tracker.process_order_update(order_fill_update_2)

        # Check order is not longer being actively tracked
        self.assertIsNone(
            self.tracker.fetch_tracked_order(order.client_order_id))

        cached_order: InFlightOrder = self.tracker.fetch_cached_order(
            order.client_order_id)
        self.assertEqual(cached_order.current_state,
                         order_fill_update_2.new_state)
        self.assertTrue(cached_order.is_done)

        # Check that Logger has logged the appropriate logs
        self.assertTrue(
            self._is_logged(
                "INFO",
                f"The {order.trade_type.name.upper()} order {order.client_order_id} amounting to "
                f"{order_fill_update_1.executed_amount_base}/{order.amount} {order.base_asset} has been filled.",
            ))
        self.assertTrue(
            self._is_logged(
                "INFO",
                f"The {order.trade_type.name.upper()} order {order.client_order_id} amounting to "
                f"{order_fill_update_2.executed_amount_base}/{order.amount} {order.base_asset} has been filled.",
            ))
        self.assertTrue(
            self._is_logged(
                "INFO",
                f"{order.trade_type.name.upper()} order {order.client_order_id} completely filled."
            ))

        self.assertEqual(3, len(self.connector.event_logs))
        order_fill_events: List[OrderFilledEvent] = [
            event for event in self.connector.event_logs
            if isinstance(event, OrderFilledEvent)
        ]
        order_completed_events: List[BuyOrderCompletedEvent] = [
            event for event in self.connector.event_logs
            if isinstance(event, BuyOrderCompletedEvent)
        ]

        self.assertEqual(2, len(order_fill_events))
        self.assertEqual(1, len(order_completed_events))

    def test_process_trade_update_trigger_filled_event_flat_fee(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"),
            price=Decimal("1.0"),
            initial_state=OrderState.OPEN,
        )
        self.tracker.start_tracking_order(order)

        trade_filled_price: Decimal = Decimal("0.5")
        trade_filled_amount: Decimal = order.amount / Decimal("2.0")
        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_asset=self.base_asset,
            fee_paid=fee_paid,
            fill_timestamp=1,
        )

        self.tracker.process_trade_update(trade_update)

        self.assertTrue(
            self._is_logged(
                "INFO",
                f"The {order.trade_type.name.upper()} order {order.client_order_id} amounting to "
                f"{trade_filled_amount}/{order.amount} {order.base_asset} has been filled.",
            ))

        self.assertEqual(1, len(self.connector.event_logs))
        order_filled_event: OrderFilledEvent = self.connector.event_logs[0]

        self.assertEqual(order_filled_event.order_id, order.client_order_id)
        self.assertEqual(order_filled_event.price, trade_update.fill_price)
        self.assertEqual(order_filled_event.amount,
                         trade_update.fill_base_amount)
        self.assertEqual(
            order_filled_event.trade_fee,
            AddedToCostTradeFee(
                flat_fees=[TokenAmount(self.base_asset, fee_paid)]))

    def test_process_trade_update_trigger_filled_event_trade_fee_percent(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"),
            price=Decimal("1.0"),
            initial_state=OrderState.OPEN,
            trade_fee_percent=self.trade_fee_percent,
        )
        self.tracker.start_tracking_order(order)

        order_filled_price: Decimal = Decimal("0.5")
        order_filled_amount: Decimal = order.amount / Decimal("2.0")
        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=order_filled_price,
            fill_base_amount=order_filled_amount,
            fill_quote_amount=order_filled_price * order_filled_amount,
            fee_asset=self.base_asset,
            fill_timestamp=1,
        )

        self.tracker.process_trade_update(trade_update)

        self.assertTrue(
            self._is_logged(
                "INFO",
                f"The {order.trade_type.name.upper()} order {order.client_order_id} amounting to "
                f"{order_filled_amount}/{order.amount} {order.base_asset} has been filled.",
            ))

        self.assertEqual(1, len(self.connector.event_logs))
        order_filled_event: OrderFilledEvent = self.connector.event_logs[0]

        self.assertEqual(order_filled_event.order_id, order.client_order_id)
        self.assertEqual(order_filled_event.price, trade_update.fill_price)
        self.assertEqual(order_filled_event.amount,
                         trade_update.fill_base_amount)
        self.assertEqual(order_filled_event.trade_fee,
                         AddedToCostTradeFee(self.trade_fee_percent))

    def test_process_trade_update_trigger_filled_event_update_status_when_completely_filled(
            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"),
            price=Decimal("1.0"),
            initial_state=OrderState.OPEN,
        )
        self.tracker.start_tracking_order(order)

        fee_paid: Decimal = self.trade_fee_percent * order.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=order.price,
            fill_base_amount=order.amount,
            fill_quote_amount=order.price * order.amount,
            fee_asset=self.base_asset,
            fee_paid=fee_paid,
            fill_timestamp=1,
        )

        self.tracker.process_trade_update(trade_update)

        fetched_order: InFlightOrder = self.tracker.fetch_order(
            order.client_order_id)
        self.assertTrue(fetched_order.is_filled)
        self.assertNotIn(fetched_order.client_order_id,
                         self.tracker.active_orders)
        self.assertIn(fetched_order.client_order_id,
                      self.tracker.cached_orders)

        self.assertTrue(
            self._is_logged(
                "INFO",
                f"The {order.trade_type.name.upper()} order {order.client_order_id} amounting to "
                f"{order.amount}/{order.amount} {order.base_asset} has been filled.",
            ))

        self.assertEqual(2, len(self.connector.event_logs))

        order_filled_event: Optional[OrderFilledEvent] = None
        order_completed_event: Optional[BuyOrderCompletedEvent] = None
        for event in self.connector.event_logs:
            if isinstance(event, OrderFilledEvent):
                order_filled_event = event
            if isinstance(event, BuyOrderCompletedEvent):
                order_completed_event = event

        self.assertIsNotNone(order_filled_event)
        self.assertIsNotNone(order_completed_event)

        self.assertEqual(order_filled_event.order_id, order.client_order_id)
        self.assertEqual(order_filled_event.price, trade_update.fill_price)
        self.assertEqual(order_filled_event.amount,
                         trade_update.fill_base_amount)
        self.assertEqual(
            order_filled_event.trade_fee,
            AddedToCostTradeFee(
                flat_fees=[TokenAmount(self.base_asset, fee_paid)]))

        self.assertEqual(order_completed_event.order_id, order.client_order_id)
        self.assertEqual(order_completed_event.exchange_order_id,
                         order.exchange_order_id)
        self.assertEqual(order_completed_event.base_asset_amount, order.amount)
        self.assertEqual(
            order_completed_event.quote_asset_amount,
            trade_update.fill_price * trade_update.fill_base_amount)
        self.assertEqual(order_completed_event.fee_amount,
                         trade_update.fee_paid)

    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"),
            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)

        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_asset=self.base_asset,
            fee_paid=fee_paid,
            fill_timestamp=2,
        )

        self.tracker.process_trade_update(trade_update)
        self.assertEqual(0, len(self.tracker.active_orders))
        self.assertEqual(1, len(self.tracker.cached_orders))

    def test_process_order_not_found_invalid_order(self):
        self.assertEqual(0, len(self.tracker.active_orders))

        unknown_order_id = "UNKNOWN_ORDER_ID"
        self.tracker.process_order_not_found(unknown_order_id)

        self._is_logged(
            "DEBUG",
            f"Order is not/no longer being tracked ({unknown_order_id})")

    def test_process_order_not_found_does_not_exceed_limit(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"),
            price=Decimal("1.0"),
            initial_state=OrderState.OPEN,
        )
        self.tracker.start_tracking_order(order)

        self.tracker.process_order_not_found(order.client_order_id)

        self.assertIn(order.client_order_id, self.tracker.active_orders)
        self.assertIn(order.client_order_id,
                      self.tracker._order_not_found_records)
        self.assertEqual(
            1, self.tracker._order_not_found_records[order.client_order_id])

    def test_process_order_not_found_exceeded_limit(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"),
            price=Decimal("1.0"),
            initial_state=OrderState.OPEN,
        )
        self.tracker.start_tracking_order(order)

        self.tracker._order_not_found_records[order.client_order_id] = 3
        self.tracker.process_order_not_found(order.client_order_id)

        self.assertNotIn(order.client_order_id, self.tracker.active_orders)

    def test_restore_tracking_states_only_registers_open_orders(self):
        orders = []
        orders.append(
            InFlightOrder(
                client_order_id="OID1",
                exchange_order_id="EOID1",
                trading_pair=self.trading_pair,
                order_type=OrderType.LIMIT,
                trade_type=TradeType.BUY,
                amount=Decimal("1000.0"),
                price=Decimal("1.0"),
            ))
        orders.append(
            InFlightOrder(client_order_id="OID2",
                          exchange_order_id="EOID2",
                          trading_pair=self.trading_pair,
                          order_type=OrderType.LIMIT,
                          trade_type=TradeType.BUY,
                          amount=Decimal("1000.0"),
                          price=Decimal("1.0"),
                          initial_state=OrderState.CANCELLED))
        orders.append(
            InFlightOrder(client_order_id="OID3",
                          exchange_order_id="EOID3",
                          trading_pair=self.trading_pair,
                          order_type=OrderType.LIMIT,
                          trade_type=TradeType.BUY,
                          amount=Decimal("1000.0"),
                          price=Decimal("1.0"),
                          initial_state=OrderState.FILLED))
        orders.append(
            InFlightOrder(client_order_id="OID4",
                          exchange_order_id="EOID4",
                          trading_pair=self.trading_pair,
                          order_type=OrderType.LIMIT,
                          trade_type=TradeType.BUY,
                          amount=Decimal("1000.0"),
                          price=Decimal("1.0"),
                          initial_state=OrderState.FAILED))

        tracking_states = {
            order.client_order_id: order.to_json()
            for order in orders
        }

        self.tracker.restore_tracking_states(tracking_states)

        self.assertIn("OID1", self.tracker.active_orders)
        self.assertNotIn("OID2", self.tracker.all_orders)
        self.assertNotIn("OID3", self.tracker.all_orders)
        self.assertNotIn("OID4", self.tracker.all_orders)
示例#6
0
    def restore_market_states(self, config_file_path: str, market: ConnectorBase):
        with self._sql_manager.get_new_session() as session:
            market_states: Optional[MarketState] = self.get_market_states(config_file_path, market, session=session)

            if market_states is not None:
                market.restore_tracking_states(market_states.saved_state)