Ejemplo n.º 1
0
 def setUp(self) -> None:
     super().setUp()
     self.mocking_assistant = NetworkMockingAssistant()
     self.event_listener = EventLogger()
     not_a_real_secret = "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg=="
     self.exchange = KrakenExchange(
         kraken_api_key="someKey",
         kraken_secret_key=not_a_real_secret,
         trading_pairs=[self.trading_pair],
     )
     self.start_time = 1
     self.clock = Clock(clock_mode=ClockMode.BACKTEST,
                        start_time=self.start_time)
Ejemplo n.º 2
0
    def setUpClass(cls):
        cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop()

        cls.clock: Clock = Clock(ClockMode.REALTIME)
        cls.market: KrakenExchange = KrakenExchange(conf.kraken_api_key,
                                                    conf.kraken_secret_key,
                                                    trading_pairs=[PAIR])

        cls.count = 0

        print("Initializing Kraken market... this will take about a minute. ")
        cls.clock.add_iterator(cls.market)
        cls.stack = contextlib.ExitStack()
        cls._clock = cls.stack.enter_context(cls.clock)
        cls.ev_loop.run_until_complete(cls.wait_til_ready())
        print("Ready.")
Ejemplo n.º 3
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
        recorder: MarketsRecorder = MarketsRecorder(sql, [self.market],
                                                    config_path, strategy_name)
        recorder.start()

        try:
            self.assertEqual(0, len(self.market.tracking_states))

            # Try to put limit buy order for 0.02 ETH at fraction of USDC market price, and watch for order creation event.
            order_id = self.underpriced_limit_buy()
            [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.events:
                self.market.remove_listener(event_tag, self.market_logger)
            self.market: KrakenExchange = KrakenExchange(
                conf.kraken_api_key,
                conf.kraken_secret_key,
                trading_pairs=[PAIR])
            for event_tag in self.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.market.cancel(PAIR, order_id)
            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(PAIR, order_id)
                self.run_parallel(
                    self.market_logger.wait_for(OrderCancelledEvent))

            recorder.stop()
            unlink(self.db_path)
Ejemplo n.º 4
0
class KrakenExchangeTest(unittest.TestCase):
    @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}"

    def setUp(self) -> None:
        super().setUp()
        self.mocking_assistant = NetworkMockingAssistant()
        self.event_listener = EventLogger()
        not_a_real_secret = "kQH5HW/8p1uGOVjbgWA7FunAmGO8lsSUXNsu3eow76sz84Q18fWxnyRzBHCd3pd5nE9qa99HAZtuZuj6F1huXg=="
        self.exchange = KrakenExchange(
            kraken_api_key="someKey",
            kraken_secret_key=not_a_real_secret,
            trading_pairs=[self.trading_pair],
        )
        self.start_time = 1
        self.clock = Clock(clock_mode=ClockMode.BACKTEST,
                           start_time=self.start_time)

    def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1):
        ret = self.ev_loop.run_until_complete(
            asyncio.wait_for(coroutine, timeout))
        return ret

    def simulate_trading_rules_initialized(self, mocked_api):
        url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ASSET_PAIRS_PATH_URL}"
        resp = self.get_asset_pairs_mock()
        mocked_api.get(url, body=json.dumps(resp))

        self.async_run_with_timeout(self.exchange._update_trading_rules(),
                                    timeout=2)

    @staticmethod
    def register_sent_request(requests_list, url, **kwargs):
        requests_list.append((url, kwargs))

    def get_asset_pairs_mock(self) -> Dict:
        asset_pairs = {
            "error": [],
            "result": {
                f"X{self.base_asset}{self.quote_asset}": {
                    "altname": f"{self.base_asset}{self.quote_asset}",
                    "wsname": f"{self.base_asset}/{self.quote_asset}",
                    "aclass_base": "currency",
                    "base": f"{self.base_asset}",
                    "aclass_quote": "currency",
                    "quote": f"{self.quote_asset}",
                    "lot": "unit",
                    "pair_decimals": 5,
                    "lot_decimals": 8,
                    "lot_multiplier": 1,
                    "leverage_buy": [
                        2,
                        3,
                    ],
                    "leverage_sell": [
                        2,
                        3,
                    ],
                    "fees": [
                        [0, 0.26],
                        [50000, 0.24],
                    ],
                    "fees_maker": [
                        [0, 0.16],
                        [50000, 0.14],
                    ],
                    "fee_volume_currency": "ZUSD",
                    "margin_call": 80,
                    "margin_stop": 40,
                    "ordermin": "0.005"
                },
            }
        }
        return asset_pairs

    def get_balances_mock(self, base_asset_balance: float,
                          quote_asset_balance: float) -> Dict:
        balances = {
            "error": [],
            "result": {
                self.base_asset: str(base_asset_balance),
                self.quote_asset: str(quote_asset_balance),
                "USDT": "171288.6158",
            }
        }
        return balances

    def get_open_orders_mock(self, quantity: float, price: float,
                             order_type: str) -> Dict:
        open_orders = {
            "error": [],
            "result": {
                "open": {
                    "OQCLML-BW3P3-BUCMWZ":
                    self.get_order_status_mock(quantity,
                                               price,
                                               order_type,
                                               status="open"),
                }
            }
        }
        return open_orders

    def get_query_orders_mock(self, exchange_id: str, quantity: float,
                              price: float, order_type: str,
                              status: str) -> Dict:
        query_orders = {
            "error": [],
            "result": {
                exchange_id:
                self.get_order_status_mock(quantity, price, order_type, status)
            }
        }
        return query_orders

    def get_order_status_mock(self, quantity: float, price: float,
                              order_type: str, status: str) -> Dict:
        order_status = {
            "refid": None,
            "userref": 0,
            "status": status,
            "opentm": 1616666559.8974,
            "starttm": 0,
            "expiretm": 0,
            "descr": {
                "pair": f"{self.base_asset}{self.quote_asset}",
                "type": order_type,
                "ordertype": "limit",
                "price": str(price),
                "price2": "0",
                "leverage": "none",
                "order":
                f"buy {quantity} {self.base_asset}{self.quote_asset} @ limit {price}",
                "close": ""
            },
            "vol": str(quantity),
            "vol_exec": "0",
            "cost": str(price * quantity),
            "fee": "0.00000",
            "price": str(price),
            "stopprice": "0.00000",
            "limitprice": "0.00000",
            "misc": "",
            "oflags": "fciq",
            "trades": ["TCCCTY-WE2O6-P3NB37"]
        }
        return order_status

    def get_order_placed_mock(self, exchange_id: str, quantity: float,
                              price: float, order_type: str) -> Dict:
        order_placed = {
            "error": [],
            "result": {
                "descr": {
                    "order":
                    f"{order_type} {quantity} {self.base_asset}{self.quote_asset}"
                    f" @ limit {price} with 2:1 leverage",
                },
                "txid": [exchange_id]
            }
        }
        return order_placed

    @aioresponses()
    def test_get_asset_pairs(self, mocked_api):
        url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ASSET_PAIRS_PATH_URL}"
        resp = self.get_asset_pairs_mock()
        mocked_api.get(url, body=json.dumps(resp))

        ret = self.async_run_with_timeout(self.exchange.get_asset_pairs())

        self.assertIn(self.trading_pair, ret)
        self.assertEqual(
            ret[self.trading_pair],
            resp["result"]
            [f"X{self.base_asset}{self.quote_asset}"]  # shallow comparison is ok
        )

    @aioresponses()
    def test_update_balances(self, mocked_api):
        url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ASSET_PAIRS_PATH_URL}"
        resp = self.get_asset_pairs_mock()
        mocked_api.get(url, body=json.dumps(resp))

        url = f"{CONSTANTS.BASE_URL}{CONSTANTS.BALANCE_PATH_URL}"
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))
        resp = self.get_balances_mock(base_asset_balance=10,
                                      quote_asset_balance=20)
        mocked_api.post(regex_url, body=json.dumps(resp))

        url = f"{CONSTANTS.BASE_URL}{CONSTANTS.OPEN_ORDERS_PATH_URL}"
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))
        resp = self.get_open_orders_mock(quantity=1, price=2, order_type="buy")
        mocked_api.post(regex_url, body=json.dumps(resp))

        self.async_run_with_timeout(self.exchange._update_balances())

        self.assertEqual(self.exchange.available_balances[self.quote_asset],
                         Decimal("18"))

    @aioresponses()
    def test_update_order_status_order_closed(self, mocked_api):
        order_id = "someId"
        exchange_id = "someExchangeId"

        url = f"{CONSTANTS.BASE_URL}{CONSTANTS.QUERY_ORDERS_PATH_URL}"
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))
        resp = self.get_query_orders_mock(exchange_id,
                                          quantity=1,
                                          price=2,
                                          order_type="buy",
                                          status="closed")
        mocked_api.post(regex_url, body=json.dumps(resp))

        self.exchange.start_tracking_order(
            order_id=order_id,
            exchange_order_id=exchange_id,
            trading_pair=self.trading_pair,
            trade_type=TradeType.BUY,
            price=2,
            amount=1,
            order_type=OrderType.LIMIT,
            userref=1,
        )
        self.exchange.add_listener(MarketEvent.BuyOrderCompleted,
                                   self.event_listener)

        self.async_run_with_timeout(self.exchange._update_order_status())

        self.assertEqual(len(self.event_listener.event_log), 1)
        self.assertTrue(
            isinstance(self.event_listener.event_log[0],
                       BuyOrderCompletedEvent))
        self.assertNotIn(order_id, self.exchange.in_flight_orders)

    @aioresponses()
    def test_check_network_success(self, mock_api):
        url = f"{CONSTANTS.BASE_URL}{CONSTANTS.TIME_PATH_URL}"
        resp = {"status": 200, "result": []}
        mock_api.get(url, body=json.dumps(resp))

        ret = self.async_run_with_timeout(
            coroutine=self.exchange.check_network())

        self.assertEqual(ret, NetworkStatus.CONNECTED)

    @aioresponses()
    def test_check_network_raises_cancelled_error(self, mock_api):
        url = f"{CONSTANTS.BASE_URL}{CONSTANTS.TIME_PATH_URL}"
        mock_api.get(url, exception=asyncio.CancelledError)

        with self.assertRaises(asyncio.CancelledError):
            self.async_run_with_timeout(
                coroutine=self.exchange.check_network())

    @aioresponses()
    def test_check_network_not_connected_for_error_status(self, mock_api):
        url = f"{CONSTANTS.BASE_URL}{CONSTANTS.TIME_PATH_URL}"
        resp = {"status": 405, "result": []}
        mock_api.get(url, status=405, body=json.dumps(resp))

        ret = self.async_run_with_timeout(
            coroutine=self.exchange.check_network())

        self.assertEqual(ret, NetworkStatus.NOT_CONNECTED)

    @aioresponses()
    def test_get_open_orders_with_userref(self, mocked_api):
        sent_messages = []
        url = f"{CONSTANTS.BASE_URL}{CONSTANTS.OPEN_ORDERS_PATH_URL}"
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))
        resp = self.get_open_orders_mock(quantity=1, price=2, order_type="buy")
        mocked_api.post(regex_url,
                        body=json.dumps(resp),
                        callback=partial(self.register_sent_request,
                                         sent_messages))
        userref = 1

        ret = self.async_run_with_timeout(
            self.exchange.get_open_orders_with_userref(userref))

        self.assertEqual(len(sent_messages), 1)

        sent_message = sent_messages[0][1]["data"]

        self.assertEqual(sent_message["userref"], userref)
        self.assertEqual(ret, resp["result"])  # shallow comparison ok

    @aioresponses()
    def test_get_order(self, mocked_api):
        sent_messages = []
        order_id = "someId"
        exchange_id = "someExchangeId"

        url = f"{CONSTANTS.BASE_URL}{CONSTANTS.QUERY_ORDERS_PATH_URL}"
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))
        resp = self.get_query_orders_mock(exchange_id,
                                          quantity=1,
                                          price=2,
                                          order_type="buy",
                                          status="closed")
        mocked_api.post(regex_url,
                        body=json.dumps(resp),
                        callback=partial(self.register_sent_request,
                                         sent_messages))

        self.exchange.start_tracking_order(
            order_id=order_id,
            exchange_order_id=exchange_id,
            trading_pair=self.trading_pair,
            trade_type=TradeType.BUY,
            price=2,
            amount=1,
            order_type=OrderType.LIMIT,
            userref=1,
        )
        ret = self.async_run_with_timeout(
            self.exchange.get_order(client_order_id=order_id))

        self.assertEqual(len(sent_messages), 1)

        sent_message = sent_messages[0][1]["data"]

        self.assertEqual(sent_message["txid"], exchange_id)
        self.assertEqual(ret, resp["result"])  # shallow comparison ok

    @aioresponses()
    def test_execute_buy(self, mocked_api):
        self.exchange.start(self.clock, self.start_time)
        self.simulate_trading_rules_initialized(mocked_api)

        order_id = "someId"
        exchange_id = "someExchangeId"
        userref = 1
        quantity = 1
        price = 2

        url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ADD_ORDER_PATH_URL}"
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))
        resp = self.get_order_placed_mock(exchange_id,
                                          quantity,
                                          price,
                                          order_type="buy")
        mocked_api.post(regex_url, body=json.dumps(resp))

        self.exchange.add_listener(MarketEvent.BuyOrderCreated,
                                   self.event_listener)
        self.async_run_with_timeout(
            self.exchange.execute_buy(
                order_id,
                self.trading_pair,
                amount=Decimal(quantity),
                order_type=OrderType.LIMIT,
                price=Decimal(price),
                userref=userref,
            ))

        self.assertEqual(len(self.event_listener.event_log), 1)
        self.assertTrue(
            isinstance(self.event_listener.event_log[0], BuyOrderCreatedEvent))
        self.assertIn(order_id, self.exchange.in_flight_orders)

    @aioresponses()
    def test_execute_sell(self, mocked_api):
        order_id = "someId"
        exchange_id = "someExchangeId"
        userref = 1
        quantity = 1
        price = 2

        url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ADD_ORDER_PATH_URL}"
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))
        resp = self.get_order_placed_mock(exchange_id,
                                          quantity,
                                          price,
                                          order_type="sell")
        mocked_api.post(regex_url, body=json.dumps(resp))

        self.exchange.start(self.clock, self.start_time)
        self.simulate_trading_rules_initialized(mocked_api)
        self.exchange.add_listener(MarketEvent.SellOrderCreated,
                                   self.event_listener)
        self.async_run_with_timeout(
            self.exchange.execute_sell(
                order_id,
                self.trading_pair,
                amount=Decimal(quantity),
                order_type=OrderType.LIMIT,
                price=Decimal(price),
                userref=userref,
            ))

        self.assertEqual(len(self.event_listener.event_log), 1)
        self.assertTrue(
            isinstance(self.event_listener.event_log[0],
                       SellOrderCreatedEvent))
        self.assertIn(order_id, self.exchange.in_flight_orders)

    @aioresponses()
    def test_execute_cancel(self, mocked_api):
        order_id = "someId"
        exchange_id = "someExchangeId"

        url = f"{CONSTANTS.BASE_URL}{CONSTANTS.CANCEL_ORDER_PATH_URL}"
        regex_url = re.compile(f"^{url}".replace(".",
                                                 r"\.").replace("?", r"\?"))
        resp = {"error": [], "result": {"count": 1}}
        mocked_api.post(regex_url, body=json.dumps(resp))

        self.exchange.start_tracking_order(
            order_id=order_id,
            exchange_order_id=exchange_id,
            trading_pair=self.trading_pair,
            trade_type=TradeType.BUY,
            price=2,
            amount=1,
            order_type=OrderType.LIMIT,
            userref=1,
        )
        self.exchange.in_flight_orders[order_id].update_exchange_order_id(
            exchange_id)
        self.exchange.in_flight_orders[order_id].last_state = "pending"
        self.exchange.add_listener(MarketEvent.OrderCancelled,
                                   self.event_listener)
        ret = self.async_run_with_timeout(
            self.exchange.execute_cancel(self.trading_pair, order_id))

        self.assertEqual(len(self.event_listener.event_log), 1)
        self.assertTrue(
            isinstance(self.event_listener.event_log[0], OrderCancelledEvent))
        self.assertNotIn(order_id, self.exchange.in_flight_orders)
        self.assertEqual(ret["origClientOrderId"], order_id)

    def test_execute_cancel_ignores_local_orders(self):
        order_id = "someId"
        exchange_id = "someExchangeId"

        self.exchange.start_tracking_order(
            order_id=order_id,
            exchange_order_id=exchange_id,
            trading_pair=self.trading_pair,
            trade_type=TradeType.BUY,
            price=2,
            amount=1,
            order_type=OrderType.LIMIT,
            userref=1,
        )

        with self.assertRaises(KrakenInFlightOrderNotCreated):
            self.async_run_with_timeout(
                self.exchange.execute_cancel(self.trading_pair, order_id))