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)
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
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)
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)
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)
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)
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
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
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.")
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})
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
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()