def place_order(self,
                 is_buy,
                 trading_pair,
                 amount,
                 order_type,
                 price,
                 nonce,
                 fixture_resp,
                 fixture_ws_1=None,
                 fixture_ws_2=None):
     order_id = None
     if API_MOCK_ENABLED:
         resp = self.order_response(fixture_resp, nonce,
                                    'buy' if is_buy else 'sell',
                                    trading_pair)
         self.web_app.update_response("post", self.base_api_url,
                                      "/api/v3/order", resp)
     if is_buy:
         order_id = self.market.buy(trading_pair, amount, order_type, price)
     else:
         order_id = self.market.sell(trading_pair, amount, order_type,
                                     price)
     if API_MOCK_ENABLED and fixture_ws_1 is not None and fixture_ws_2 is not None:
         data = self.fixture(fixture_ws_1, c=order_id)
         HummingWsServerFactory.send_json_threadsafe(self._ws_user_url,
                                                     data,
                                                     delay=0.1)
         data = self.fixture(fixture_ws_2, c=order_id)
         HummingWsServerFactory.send_json_threadsafe(self._ws_user_url,
                                                     data,
                                                     delay=0.11)
     return order_id
    def test_cancel_all(self):
        trading_pair = "ETH-USDC"
        bid_price: Decimal = self.market.get_price(trading_pair, True) * Decimal("0.5")
        ask_price: Decimal = self.market.get_price(trading_pair, False) * 2
        amount: Decimal = 10 / bid_price
        quantized_amount: Decimal = self.market.quantize_order_amount(trading_pair, amount)

        # Intentionally setting invalid price to prevent getting filled
        quantize_bid_price: Decimal = self.market.quantize_order_price(trading_pair, bid_price * Decimal("0.7"))
        quantize_ask_price: Decimal = self.market.quantize_order_price(trading_pair, ask_price * Decimal("1.5"))

        _, exch_order_id = self.place_order(True, trading_pair, quantized_amount, OrderType.LIMIT_MAKER, quantize_bid_price,
                                            10001, FixtureCoinbasePro.OPEN_BUY_LIMIT_ORDER, FixtureCoinbasePro.WS_ORDER_OPEN)
        _, exch_order_id_2 = self.place_order(False, trading_pair, quantized_amount, OrderType.LIMIT_MAKER,
                                              quantize_ask_price, 10002, FixtureCoinbasePro.OPEN_SELL_LIMIT_ORDER,
                                              FixtureCoinbasePro.WS_ORDER_OPEN)
        self.run_parallel(asyncio.sleep(1))

        if API_MOCK_ENABLED:
            self.web_app.update_response("delete", API_BASE_URL, f"/orders/{exch_order_id}", exch_order_id)
            self.web_app.update_response("delete", API_BASE_URL, f"/orders/{exch_order_id_2}", exch_order_id_2)
        [cancellation_results] = self.run_parallel(self.market.cancel_all(5))
        if API_MOCK_ENABLED:
            resp = FixtureCoinbasePro.WS_ORDER_CANCELLED.copy()
            resp["order_id"] = exch_order_id
            HummingWsServerFactory.send_json_threadsafe(WS_BASE_URL, resp, delay=0.1)
            resp = FixtureCoinbasePro.WS_ORDER_CANCELLED.copy()
            resp["order_id"] = exch_order_id_2
            HummingWsServerFactory.send_json_threadsafe(WS_BASE_URL, resp, delay=0.11)
        for cr in cancellation_results:
            self.assertEqual(cr.success, True)
Example #3
0
 def place_order(self, is_buy, trading_pair, amount, order_type, price,
                 nonce, post_resp, ws_resp):
     global EXCHANGE_ORDER_ID
     order_id, exch_order_id = None, None
     if API_MOCK_ENABLED:
         exch_order_id = f"BITTREX_{EXCHANGE_ORDER_ID}"
         EXCHANGE_ORDER_ID += 1
         self._t_nonce_mock.return_value = nonce
         resp = post_resp.copy()
         resp["id"] = exch_order_id
         side = 'buy' if is_buy else 'sell'
         resp["direction"] = side.upper()
         resp["type"] = order_type.name.upper()
         if order_type == OrderType.LIMIT:
             del resp["limit"]
         self.web_app.update_response("post", API_BASE_URL, "/v3/orders",
                                      resp)
     if is_buy:
         order_id = self.market.buy(trading_pair, amount, order_type, price)
     else:
         order_id = self.market.sell(trading_pair, amount, order_type,
                                     price)
     if API_MOCK_ENABLED:
         resp = ws_resp.copy()
         resp["content"]["o"]["OU"] = exch_order_id
         HummingWsServerFactory.send_json_threadsafe(WS_BASE_URL,
                                                     resp,
                                                     delay=1.0)
     return order_id, exch_order_id
Example #4
0
    def test_cancel_order(self):
        self.assertGreater(self.market.get_balance("USDC"), 16000)
        trading_pair = "WETH-USDC"
        bid_price: Decimal = self.market.get_price(trading_pair, True)
        amount: Decimal = Decimal("40.0")

        # Intentionally setting price far away from best ask
        client_order_id = self.place_order(True, "WETH-USDC", amount,
                                           OrderType.LIMIT_MAKER,
                                           bid_price * Decimal('0.5'), 10001,
                                           FixtureDydx.BUY_LIMIT_ORDER,
                                           FixtureDydx.WS_AFTER_BUY_1)
        self.run_parallel(asyncio.sleep(1.0))
        self.market.cancel(trading_pair, client_order_id)
        if API_MOCK_ENABLED:
            HummingWsServerFactory.send_json_threadsafe(
                self._ws_user_url, FixtureDydx.WS_AFTER_CANCEL_BUY, delay=0.1)
        [order_cancelled_event] = self.run_parallel(
            self.market_logger.wait_for(OrderCancelledEvent))

        order_cancelled_event: OrderCancelledEvent = order_cancelled_event

        self.run_parallel(asyncio.sleep(6.0))
        self.assertEqual(0, len(self.market.limit_orders))
        self.assertEqual(client_order_id, order_cancelled_event.order_id)
 def cancel_order(self, trading_pair, order_id, exchange_order_id, fixture_ws):
     if API_MOCK_ENABLED:
         self.web_app.update_response("delete", API_BASE_URL, f"/orders/{exchange_order_id}", exchange_order_id)
     self.market.cancel(trading_pair, order_id)
     if API_MOCK_ENABLED:
         resp = fixture_ws.copy()
         resp["order_id"] = exchange_order_id
         HummingWsServerFactory.send_json_threadsafe(WS_BASE_URL, resp, delay=0.1)
Example #6
0
 def _cancel_order(self, cl_order_id):
     self.connector.cancel(self.trading_pair, cl_order_id)
     if API_MOCK_ENABLED:
         data = fixture.WS_ORDER_CANCELLED.copy()
         data["result"]["data"][0]["client_oid"] = cl_order_id
         HummingWsServerFactory.send_json_threadsafe(WSS_PRIVATE_URL,
                                                     data,
                                                     delay=0.1)
Example #7
0
 def _mock_ws_bal_update(self, token, available):
     if API_MOCK_ENABLED:
         available = float(available)
         data = fixture.WS_BALANCE.copy()
         data["result"]["data"][0]["currency"] = token
         data["result"]["data"][0]["available"] = available
         HummingWsServerFactory.send_json_threadsafe(WSS_PRIVATE_URL,
                                                     fixture.WS_BALANCE,
                                                     delay=0.1)
Example #8
0
    def test_prevent_duplicated_orders(self):
        config_path: str = "test_config"
        strategy_name: str = "test_strategy"
        sql: SQLConnectionManager = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path=self.db_path)
        buy_id: Optional[str] = None
        recorder: MarketsRecorder = MarketsRecorder(sql, [self.market], config_path, strategy_name)
        recorder.start()

        try:
            # Perform the same order twice which should produce the same exchange_order_id
            # Try to buy 1 LINK from the exchange, and watch for completion event.
            bid_price: Decimal = self.market.get_price("LINK-ETH", True)
            amount: Decimal = 1
            buy_id = self.place_order(True, "LINK-ETH", amount, OrderType.LIMIT, bid_price, self.get_current_nonce(),
                                      FixtureBinance.BUY_LIMIT_ORDER, FixtureBinance.WS_AFTER_BUY_1,
                                      FixtureBinance.WS_AFTER_BUY_2)
            [buy_order_completed_event] = self.run_parallel(self.market_logger.wait_for(BuyOrderCompletedEvent))

            self.market_logger.clear()

            # Simulate that order is still in in_flight_orders
            order_json = {"client_order_id": buy_id,
                          "exchange_order_id": str(FixtureBinance.WS_AFTER_BUY_2['t']),
                          "trading_pair": "LINK-ETH",
                          "order_type": "MARKET",
                          "trade_type": "BUY",
                          "price": bid_price,
                          "amount": amount,
                          "last_state": "NEW",
                          "executed_amount_base": "0",
                          "executed_amount_quote": "0",
                          "fee_asset": "LINK",
                          "fee_paid": "0.0"}
            self.market.restore_tracking_states({buy_id: order_json})
            self.market.in_flight_orders.get(buy_id).trade_id_set.add(str(FixtureBinance.WS_AFTER_BUY_2['t']))
            # Simulate incoming responses as if buy_id is executed again
            data = self.fixture(FixtureBinance.WS_AFTER_BUY_2, c=buy_id)
            HummingWsServerFactory.send_json_threadsafe(self._ws_user_url, data, delay=0.11)
            # Will wait, but no order filled event should be triggered because order is ignored
            self.run_parallel(asyncio.sleep(1))
            # Query the persisted trade logs
            trade_fills: List[TradeFill] = recorder.get_trades_for_config(config_path)
            buy_fills: List[TradeFill] = [t for t in trade_fills if t.trade_type == "BUY"]
            exchange_trade_id = FixtureBinance.WS_AFTER_BUY_2['t']
            self.assertEqual(len([bf for bf in buy_fills if int(bf.exchange_trade_id) == exchange_trade_id]), 1)

            buy_id = None

        finally:
            if buy_id is not None:
                self.market.cancel("LINK-ETH", buy_id)
                self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent))

            recorder.stop()
            os.unlink(self.db_path)
Example #9
0
    def test_limit_makers_unfilled(self):
        self.assertGreater(self.market.get_balance("USDC"), 16000)
        trading_pair = "WETH-USDC"
        amount: Decimal = Decimal("40.0")
        bid_price: Decimal = self.market.get_price(trading_pair, True)
        buy_order_id: str = self.place_order(True, "WETH-USDC", amount,
                                             OrderType.LIMIT_MAKER,
                                             bid_price * Decimal('0.5'), 10001,
                                             FixtureDydx.BUY_LIMIT_MAKER_ORDER,
                                             FixtureDydx.WS_AFTER_BUY_1)
        self.run_parallel(asyncio.sleep(6.0))
        self.market.cancel(trading_pair, buy_order_id)

        if API_MOCK_ENABLED:
            HummingWsServerFactory.send_json_threadsafe(
                self._ws_user_url, FixtureDydx.WS_AFTER_CANCEL_BUY, delay=0.1)
        [order_cancelled_event] = self.run_parallel(
            self.market_logger.wait_for(OrderCancelledEvent))
 def place_order(self, is_buy, trading_pair, amount, order_type, price, nonce, fixture_resp, fixture_ws):
     order_id, exch_order_id = None, None
     if API_MOCK_ENABLED:
         self._t_nonce_mock.return_value = nonce
         side = 'buy' if is_buy else 'sell'
         resp = fixture_resp.copy()
         exch_order_id = resp["id"]
         resp["side"] = side
         self.web_app.update_response("post", API_BASE_URL, "/orders", resp)
     if is_buy:
         order_id = self.market.buy(trading_pair, amount, order_type, price)
     else:
         order_id = self.market.sell(trading_pair, amount, order_type, price)
     if API_MOCK_ENABLED:
         resp = fixture_ws.copy()
         resp["order_id"] = exch_order_id
         resp["side"] = side
         HummingWsServerFactory.send_json_threadsafe(WS_BASE_URL, resp, delay=0.1)
     return order_id, exch_order_id
Example #11
0
 def _place_order(self,
                  is_buy,
                  amount,
                  order_type,
                  price,
                  ex_order_id,
                  get_order_fixture=None,
                  ws_trade_fixture=None,
                  ws_order_fixture=None) -> str:
     if API_MOCK_ENABLED:
         data = fixture.PLACE_ORDER.copy()
         data["result"]["order_id"] = str(ex_order_id)
         self.web_app.update_response("post", BASE_API_URL,
                                      "/v2/private/create-order", data)
     if is_buy:
         cl_order_id = self.connector.buy(self.trading_pair, amount,
                                          order_type, price)
     else:
         cl_order_id = self.connector.sell(self.trading_pair, amount,
                                           order_type, price)
     if API_MOCK_ENABLED:
         if get_order_fixture is not None:
             data = get_order_fixture.copy()
             data["result"]["order_info"]["client_oid"] = cl_order_id
             data["result"]["order_info"]["order_id"] = ex_order_id
             self.web_app.update_response("post", BASE_API_URL,
                                          "/v2/private/get-order-detail",
                                          data)
         if ws_trade_fixture is not None:
             data = ws_trade_fixture.copy()
             data["result"]["data"][0]["order_id"] = str(ex_order_id)
             HummingWsServerFactory.send_json_threadsafe(WSS_PRIVATE_URL,
                                                         data,
                                                         delay=0.1)
         if ws_order_fixture is not None:
             data = ws_order_fixture.copy()
             data["result"]["data"][0]["order_id"] = str(ex_order_id)
             data["result"]["data"][0]["client_oid"] = cl_order_id
             HummingWsServerFactory.send_json_threadsafe(WSS_PRIVATE_URL,
                                                         data,
                                                         delay=0.12)
     return cl_order_id
Example #12
0
    def setUpClass(cls):
        global MAINNET_RPC_URL

        cls.ev_loop = asyncio.get_event_loop()

        if API_MOCK_ENABLED:
            cls.web_app = HummingWebApp.get_instance()
            cls.web_app.add_host_to_mock(BASE_API_URL, [])
            cls.web_app.start()
            cls.ev_loop.run_until_complete(cls.web_app.wait_til_started())
            cls._patcher = mock.patch("aiohttp.client.URL")
            cls._url_mock = cls._patcher.start()
            cls._url_mock.side_effect = cls.web_app.reroute_local
            cls.web_app.update_response("get", BASE_API_URL,
                                        "/v2/public/get-ticker",
                                        fixture.TICKERS)
            cls.web_app.update_response("get", BASE_API_URL,
                                        "/v2/public/get-instruments",
                                        fixture.INSTRUMENTS)
            cls.web_app.update_response("get", BASE_API_URL,
                                        "/v2/public/get-book",
                                        fixture.GET_BOOK)
            cls.web_app.update_response("post", BASE_API_URL,
                                        "/v2/private/get-account-summary",
                                        fixture.BALANCES)
            cls.web_app.update_response("post", BASE_API_URL,
                                        "/v2/private/cancel-order",
                                        fixture.CANCEL)

            HummingWsServerFactory.start_new_server(WSS_PRIVATE_URL)
            HummingWsServerFactory.start_new_server(WSS_PUBLIC_URL)
            cls._ws_patcher = unittest.mock.patch("websockets.connect",
                                                  autospec=True)
            cls._ws_mock = cls._ws_patcher.start()
            cls._ws_mock.side_effect = HummingWsServerFactory.reroute_ws_connect

        cls.clock: Clock = Clock(ClockMode.REALTIME)
        cls.connector: CryptoComExchange = CryptoComExchange(
            crypto_com_api_key=API_KEY,
            crypto_com_api_secret=API_SECRET,
            trading_pairs=[cls.trading_pair],
            trading_required=True)
        print(
            "Initializing CryptoCom market... this will take about a minute.")
        cls.clock.add_iterator(cls.connector)
        cls.stack: contextlib.ExitStack = contextlib.ExitStack()
        cls._clock = cls.stack.enter_context(cls.clock)
        if API_MOCK_ENABLED:
            HummingWsServerFactory.send_json_threadsafe(WSS_PRIVATE_URL,
                                                        fixture.WS_INITIATED,
                                                        delay=0.5)
            HummingWsServerFactory.send_json_threadsafe(WSS_PRIVATE_URL,
                                                        fixture.WS_SUBSCRIBE,
                                                        delay=0.51)
            HummingWsServerFactory.send_json_threadsafe(WSS_PRIVATE_URL,
                                                        fixture.WS_HEARTBEAT,
                                                        delay=0.52)
        cls.ev_loop.run_until_complete(cls.wait_til_ready())
        print("Ready.")
Example #13
0
    def test_cancel_all(self):
        bid_price = self.connector.get_price(self.trading_pair, True)
        ask_price = self.connector.get_price(self.trading_pair, False)
        bid_price = self.connector.quantize_order_price(
            self.trading_pair, bid_price * Decimal("0.7"))
        ask_price = self.connector.quantize_order_price(
            self.trading_pair, ask_price * Decimal("1.5"))
        amount = self.connector.quantize_order_amount(self.trading_pair,
                                                      Decimal("0.0001"))

        buy_id = self._place_order(True, amount, OrderType.LIMIT, bid_price, 1)
        sell_id = self._place_order(False, amount, OrderType.LIMIT, ask_price,
                                    2)

        self.ev_loop.run_until_complete(asyncio.sleep(1))
        asyncio.ensure_future(self.connector.cancel_all(3))
        if API_MOCK_ENABLED:
            data = fixture.WS_ORDER_CANCELLED.copy()
            data["result"]["data"][0]["client_oid"] = buy_id
            data["result"]["data"][0]["order_id"] = 1
            HummingWsServerFactory.send_json_threadsafe(WSS_PRIVATE_URL,
                                                        data,
                                                        delay=0.1)
            self.ev_loop.run_until_complete(asyncio.sleep(1))
            data = fixture.WS_ORDER_CANCELLED.copy()
            data["result"]["data"][0]["client_oid"] = sell_id
            data["result"]["data"][0]["order_id"] = 2
            HummingWsServerFactory.send_json_threadsafe(WSS_PRIVATE_URL,
                                                        data,
                                                        delay=0.11)
        self.ev_loop.run_until_complete(asyncio.sleep(3))
        cancel_events = [
            t for t in self.event_logger.event_log
            if isinstance(t, OrderCancelledEvent)
        ]
        self.assertEqual({buy_id, sell_id},
                         {o.order_id
                          for o in cancel_events})
Example #14
0
 def place_order(self,
                 is_buy,
                 trading_pair,
                 amount,
                 order_type,
                 price,
                 nonce,
                 fixture_resp,
                 fixture_ws_1=None,
                 fixture_ws_2=None,
                 fixture_ws_3=None):
     order_id = None
     if API_MOCK_ENABLED:
         resp = self.order_response(fixture_resp, nonce,
                                    'buy' if is_buy else 'sell',
                                    trading_pair)
         self.web_app.update_response("post", self.base_api_url,
                                      "/v2/orders", resp)
     if is_buy:
         order_id = self.market.buy(trading_pair, amount, order_type, price)
     else:
         order_id = self.market.sell(trading_pair, amount, order_type,
                                     price)
     if API_MOCK_ENABLED and fixture_ws_1 is not None:
         self.web_app.update_response("get",
                                      self.base_api_url,
                                      "/v2/fills",
                                      FixtureDydx.FILLS,
                                      params={
                                          "orderId": order_id,
                                          "limit": 100
                                      })
         data = self.fixture(fixture_ws_1, id=order_id)
         HummingWsServerFactory.send_json_threadsafe(self._ws_user_url,
                                                     data,
                                                     delay=0.1)
         if fixture_ws_2 is not None:
             data = self.fixture(fixture_ws_2, id=order_id)
             HummingWsServerFactory.send_json_threadsafe(self._ws_user_url,
                                                         data,
                                                         delay=0.11)
         if fixture_ws_3 is not None:
             HummingWsServerFactory.send_json_threadsafe(self._ws_user_url,
                                                         fixture_ws_3,
                                                         delay=0.1)
     return order_id
Example #15
0
    def test_order_saving_and_restoration(self):
        config_path: str = "test_config"
        strategy_name: str = "test_strategy"
        sql: SQLConnectionManager = SQLConnectionManager(
            SQLConnectionType.TRADE_FILLS, db_path=self.db_path)
        order_id: Optional[str] = None
        trading_pair: str = "WETH-USDC"

        recorder: MarketsRecorder = MarketsRecorder(sql, [self.market],
                                                    config_path, strategy_name)
        recorder.start()
        try:
            self.assertEqual(0, len(self.market.tracking_states))
            self.assertGreater(self.market.get_balance("USDC"), 16000)
            amount: Decimal = Decimal("40.0")
            current_bid_price: Decimal = self.market.get_price(
                trading_pair, True)
            bid_price: Decimal = Decimal("0.5") * current_bid_price
            quantize_bid_price: Decimal = self.market.quantize_order_price(
                trading_pair, bid_price)
            order_id = self.place_order(True, trading_pair, amount,
                                        OrderType.LIMIT, quantize_bid_price,
                                        10001, FixtureDydx.BUY_LIMIT_ORDER,
                                        FixtureDydx.WS_AFTER_BUY_1)

            [order_created_event] = self.run_parallel(
                self.market_logger.wait_for(BuyOrderCreatedEvent))
            order_created_event: BuyOrderCreatedEvent = order_created_event

            self.assertEqual(order_id, order_created_event.order_id)

            # Verify tracking states
            self.assertEqual(1, len(self.market.tracking_states))
            self.assertEqual(order_id,
                             list(self.market.tracking_states.keys())[0])

            # Verify orders from recorder
            recorded_orders: List[
                Order] = recorder.get_orders_for_config_and_market(
                    config_path, self.market)
            self.assertEqual(1, len(recorded_orders))
            self.assertEqual(order_id, recorded_orders[0].id)

            # Verify saved market states
            saved_market_states: MarketState = recorder.get_market_states(
                config_path, self.market)
            self.assertIsNotNone(saved_market_states)
            self.assertIsInstance(saved_market_states.saved_state, dict)
            self.assertGreater(len(saved_market_states.saved_state), 0)

            # Close out the current market and start another market.
            self.clock.remove_iterator(self.market)

            for event_tag in self.market_events:
                self.market.remove_listener(event_tag, self.market_logger)

            self.market: DydxExchange = DydxExchange(
                dydx_eth_private_key=PRIVATE_KEY,
                dydx_node_address=NODE_ADDRESS,
                poll_interval=10.0,
                trading_pairs=[trading_pair],
                trading_required=True)
            for event_tag in self.market_events:
                self.market.add_listener(event_tag, self.market_logger)
            recorder.stop()
            recorder = MarketsRecorder(sql, [self.market], config_path,
                                       strategy_name)
            recorder.start()
            saved_market_states = recorder.get_market_states(
                config_path, self.market)
            self.clock.add_iterator(self.market)
            self.assertEqual(0, len(self.market.limit_orders))
            self.assertEqual(0, len(self.market.tracking_states))
            self.market.restore_tracking_states(
                saved_market_states.saved_state)
            self.assertEqual(1, len(self.market.limit_orders))
            self.assertEqual(1, len(self.market.tracking_states))

            # Cancel the order and verify that the change is saved.
            self.run_parallel(asyncio.sleep(5.0))
            self.market.cancel(trading_pair, order_id)
            if API_MOCK_ENABLED:
                HummingWsServerFactory.send_json_threadsafe(
                    self._ws_user_url,
                    FixtureDydx.WS_AFTER_CANCEL_BUY,
                    delay=0.1)
            self.run_parallel(self.market_logger.wait_for(OrderCancelledEvent))
            order_id = None
            self.assertEqual(0, len(self.market.limit_orders))
            self.assertEqual(0, len(self.market.tracking_states))
            saved_market_states = recorder.get_market_states(
                config_path, self.market)
            self.assertEqual(0, len(saved_market_states.saved_state))
        finally:
            if order_id is not None:
                self.market.cancel(trading_pair, order_id)
                if API_MOCK_ENABLED:
                    HummingWsServerFactory.send_json_threadsafe(
                        self._ws_user_url,
                        FixtureDydx.WS_AFTER_CANCEL_BUY,
                        delay=0.1)
                self.run_parallel(
                    self.market_logger.wait_for(OrderCancelledEvent))

            recorder.stop()