class PreviousCommandUnitTest(unittest.TestCase):
    def setUp(self) -> None:
        super().setUp()
        self.ev_loop = asyncio.get_event_loop()

        self.async_run_with_timeout(read_system_configs_from_yml())

        self.client_config = ClientConfigMap()
        self.config_adapter = ClientConfigAdapter(self.client_config)

        self.app = HummingbotApplication(self.config_adapter)
        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        super().tearDown()

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

    def mock_user_response(self, config):
        config.value = "yes"

    def test_no_previous_strategy_found(self):
        self.config_adapter.previous_strategy = None
        self.app.previous_strategy(option="")
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                "No previous strategy found."))

    @patch(
        "hummingbot.client.command.import_command.ImportCommand.import_command"
    )
    def test_strategy_found_and_user_declines(self, import_command: MagicMock):
        strategy_name = "conf_1.yml"
        self.cli_mock_assistant.queue_prompt_reply("No")
        self.async_run_with_timeout(
            self.app.prompt_for_previous_strategy(strategy_name))
        import_command.assert_not_called()

    @patch(
        "hummingbot.client.command.import_command.ImportCommand.import_command"
    )
    def test_strategy_found_and_user_accepts(self, import_command: MagicMock):
        strategy_name = "conf_1.yml"
        self.config_adapter.previous_strategy = strategy_name
        self.cli_mock_assistant.queue_prompt_reply("Yes")
        self.async_run_with_timeout(
            self.app.prompt_for_previous_strategy(strategy_name))
        import_command.assert_called()
        self.assertTrue(import_command.call_args[0][1] == strategy_name)
Exemplo n.º 2
0
class ImportCommandTest(unittest.TestCase):
    @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher")
    def setUp(self, _: MagicMock) -> None:
        super().setUp()
        self.ev_loop = asyncio.get_event_loop()
        self.app = HummingbotApplication()
        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        super().tearDown()

    @staticmethod
    async def raise_timeout(*args, **kwargs):
        raise asyncio.TimeoutError

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

    def async_run_with_timeout_coroutine_must_raise_timeout(self, coroutine: Awaitable, timeout: float = 1):
        class DesiredError(Exception):
            pass

        async def run_coro_that_raises(coro: Awaitable):
            try:
                await coro
            except asyncio.TimeoutError:
                raise DesiredError

        try:
            self.async_run_with_timeout(run_coro_that_raises(coroutine), timeout)
        except DesiredError:  # the coroutine raised an asyncio.TimeoutError as expected
            raise asyncio.TimeoutError
        except asyncio.TimeoutError:  # the coroutine did not finish on time
            raise RuntimeError

    @patch("hummingbot.client.command.import_command.update_strategy_config_map_from_file")
    @patch("hummingbot.client.command.status_command.StatusCommand.status_check_all")
    def test_import_config_file_success(
        self, status_check_all_mock: AsyncMock, update_strategy_config_map_from_file: AsyncMock
    ):
        strategy_name = "some-strategy"
        strategy_file_name = f"{strategy_name}.yml"
        status_check_all_mock.return_value = True
        update_strategy_config_map_from_file.return_value = strategy_name

        self.async_run_with_timeout(self.app.import_config_file(strategy_file_name))
        self.assertEqual(strategy_file_name, self.app.strategy_file_name)
        self.assertEqual(strategy_name, self.app.strategy_name)
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with("\nEnter \"start\" to start market making.")
        )

    @patch("hummingbot.client.command.import_command.update_strategy_config_map_from_file")
    @patch("hummingbot.client.command.status_command.StatusCommand.status_check_all")
    def test_import_config_file_handles_network_timeouts(
        self, status_check_all_mock: AsyncMock, update_strategy_config_map_from_file: AsyncMock
    ):
        strategy_name = "some-strategy"
        strategy_file_name = f"{strategy_name}.yml"
        status_check_all_mock.side_effect = self.raise_timeout
        update_strategy_config_map_from_file.return_value = strategy_name

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.import_config_file(strategy_file_name)
            )
        self.assertEqual(None, self.app.strategy_file_name)
        self.assertEqual(None, self.app.strategy_name)
class CreateCommandTest(unittest.TestCase):
    @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher")
    def setUp(self, _: MagicMock) -> None:
        super().setUp()
        self.ev_loop = asyncio.get_event_loop()

        self.async_run_with_timeout(read_system_configs_from_yml())
        self.client_config_map = ClientConfigAdapter(ClientConfigMap())

        self.app = HummingbotApplication(
            client_config_map=self.client_config_map)
        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        super().tearDown()

    @staticmethod
    def get_async_sleep_fn(delay: float):
        async def async_sleep(*_, **__):
            await asyncio.sleep(delay)

        return async_sleep

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

    def async_run_with_timeout_coroutine_must_raise_timeout(
            self, coroutine: Awaitable, timeout: float = 1):
        class DesiredError(Exception):
            pass

        async def run_coro_that_raises(coro: Awaitable):
            try:
                await coro
            except asyncio.TimeoutError:
                raise DesiredError

        try:
            self.async_run_with_timeout(run_coro_that_raises(coroutine),
                                        timeout)
        except DesiredError:  # the coroutine raised an asyncio.TimeoutError as expected
            raise asyncio.TimeoutError
        except asyncio.TimeoutError:  # the coroutine did not finish on time
            raise RuntimeError

    @patch("shutil.copy")
    @patch("hummingbot.client.command.create_command.save_to_yml_legacy")
    @patch("hummingbot.client.config.security.Security.is_decryption_done")
    @patch(
        "hummingbot.client.command.status_command.StatusCommand.validate_required_connections"
    )
    @patch("hummingbot.core.utils.market_price.get_last_price")
    def test_prompt_for_configuration_re_prompts_on_lower_than_minimum_amount(
        self,
        get_last_price_mock: AsyncMock,
        validate_required_connections_mock: AsyncMock,
        is_decryption_done_mock: MagicMock,
        save_to_yml_mock: MagicMock,
        _: MagicMock,
    ):
        get_last_price_mock.return_value = Decimal("11")
        validate_required_connections_mock.return_value = {}
        is_decryption_done_mock.return_value = True
        config_maps = []
        save_to_yml_mock.side_effect = lambda _, cm: config_maps.append(cm)

        self.client_config_map.commands_timeout.create_command_timeout = 10
        self.client_config_map.commands_timeout.other_commands_timeout = 30
        strategy_name = "some-strategy"
        strategy_file_name = f"{strategy_name}.yml"
        base_strategy = "pure_market_making"
        self.cli_mock_assistant.queue_prompt_reply(base_strategy)  # strategy
        self.cli_mock_assistant.queue_prompt_reply("binance")  # spot connector
        self.cli_mock_assistant.queue_prompt_reply("BTC-USDT")  # trading pair
        self.cli_mock_assistant.queue_prompt_reply("1")  # bid spread
        self.cli_mock_assistant.queue_prompt_reply("1")  # ask spread
        self.cli_mock_assistant.queue_prompt_reply("30")  # order refresh time
        self.cli_mock_assistant.queue_prompt_reply(
            "0")  # unacceptable order amount
        self.cli_mock_assistant.queue_prompt_reply(
            "1")  # acceptable order amount
        self.cli_mock_assistant.queue_prompt_reply("No")  # ping pong feature

        self.async_run_with_timeout(
            self.app.prompt_for_configuration(strategy_file_name))
        self.assertEqual(strategy_file_name, self.app.strategy_file_name)
        self.assertEqual(base_strategy, self.app.strategy_name)
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg="Value must be more than 0."))

    @patch("shutil.copy")
    @patch("hummingbot.client.command.create_command.save_to_yml_legacy")
    @patch("hummingbot.client.config.security.Security.is_decryption_done")
    @patch(
        "hummingbot.client.command.status_command.StatusCommand.validate_required_connections"
    )
    @patch("hummingbot.core.utils.market_price.get_last_price")
    def test_prompt_for_configuration_accepts_zero_amount_on_get_last_price_network_timeout(
        self,
        get_last_price_mock: AsyncMock,
        validate_required_connections_mock: AsyncMock,
        is_decryption_done_mock: MagicMock,
        save_to_yml_mock: MagicMock,
        _: MagicMock,
    ):
        get_last_price_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        validate_required_connections_mock.return_value = {}
        is_decryption_done_mock.return_value = True
        config_maps = []
        save_to_yml_mock.side_effect = lambda _, cm: config_maps.append(cm)

        self.client_config_map.commands_timeout.create_command_timeout = 0.005
        self.client_config_map.commands_timeout.other_commands_timeout = 0.01
        strategy_name = "some-strategy"
        strategy_file_name = f"{strategy_name}.yml"
        base_strategy = "pure_market_making"
        self.cli_mock_assistant.queue_prompt_reply(base_strategy)  # strategy
        self.cli_mock_assistant.queue_prompt_reply("binance")  # spot connector
        self.cli_mock_assistant.queue_prompt_reply("BTC-USDT")  # trading pair
        self.cli_mock_assistant.queue_prompt_reply("1")  # bid spread
        self.cli_mock_assistant.queue_prompt_reply("1")  # ask spread
        self.cli_mock_assistant.queue_prompt_reply("30")  # order refresh time
        self.cli_mock_assistant.queue_prompt_reply("1")  # order amount
        self.cli_mock_assistant.queue_prompt_reply("No")  # ping pong feature

        self.async_run_with_timeout(
            self.app.prompt_for_configuration(strategy_file_name))
        self.assertEqual(strategy_file_name, self.app.strategy_file_name)
        self.assertEqual(base_strategy, self.app.strategy_name)

    def test_create_command_restores_config_map_after_config_stop(self):
        base_strategy = "pure_market_making"
        strategy_config = get_strategy_config_map(base_strategy)
        original_exchange = "bybit"
        strategy_config["exchange"].value = original_exchange

        self.cli_mock_assistant.queue_prompt_reply(base_strategy)  # strategy
        self.cli_mock_assistant.queue_prompt_reply("binance")  # spot connector
        self.cli_mock_assistant.queue_prompt_to_stop_config(
        )  # cancel on trading pair prompt

        self.async_run_with_timeout(self.app.prompt_for_configuration(None))
        strategy_config = get_strategy_config_map(base_strategy)

        self.assertEqual(original_exchange, strategy_config["exchange"].value)

    def test_create_command_restores_config_map_after_config_stop_on_new_file_prompt(
            self):
        base_strategy = "pure_market_making"
        strategy_config = get_strategy_config_map(base_strategy)
        original_exchange = "bybit"
        strategy_config["exchange"].value = original_exchange

        self.cli_mock_assistant.queue_prompt_reply(base_strategy)  # strategy
        self.cli_mock_assistant.queue_prompt_reply("binance")  # spot connector
        self.cli_mock_assistant.queue_prompt_reply("BTC-USDT")  # trading pair
        self.cli_mock_assistant.queue_prompt_reply("1")  # bid spread
        self.cli_mock_assistant.queue_prompt_reply("1")  # ask spread
        self.cli_mock_assistant.queue_prompt_reply("30")  # order refresh time
        self.cli_mock_assistant.queue_prompt_reply("1")  # order amount
        self.cli_mock_assistant.queue_prompt_reply("No")  # ping pong feature
        self.cli_mock_assistant.queue_prompt_to_stop_config(
        )  # cancel on new file prompt

        self.async_run_with_timeout(self.app.prompt_for_configuration(None))
        strategy_config = get_strategy_config_map(base_strategy)

        self.assertEqual(original_exchange, strategy_config["exchange"].value)

    @patch("shutil.copy")
    @patch("hummingbot.client.command.create_command.save_to_yml_legacy")
    @patch("hummingbot.client.config.security.Security.is_decryption_done")
    @patch(
        "hummingbot.client.command.status_command.StatusCommand.validate_required_connections"
    )
    @patch("hummingbot.core.utils.market_price.get_last_price")
    def test_prompt_for_configuration_handles_status_network_timeout(
        self,
        get_last_price_mock: AsyncMock,
        validate_required_connections_mock: AsyncMock,
        is_decryption_done_mock: MagicMock,
        _: MagicMock,
        __: MagicMock,
    ):
        get_last_price_mock.return_value = None
        validate_required_connections_mock.side_effect = self.get_async_sleep_fn(
            delay=0.02)
        is_decryption_done_mock.return_value = True
        self.client_config_map.commands_timeout.create_command_timeout = 0.005
        self.client_config_map.commands_timeout.other_commands_timeout = 0.01
        strategy_file_name = "some-strategy.yml"
        self.cli_mock_assistant.queue_prompt_reply(
            "pure_market_making")  # strategy
        self.cli_mock_assistant.queue_prompt_reply("binance")  # spot connector
        self.cli_mock_assistant.queue_prompt_reply("BTC-USDT")  # trading pair
        self.cli_mock_assistant.queue_prompt_reply("1")  # bid spread
        self.cli_mock_assistant.queue_prompt_reply("1")  # ask spread
        self.cli_mock_assistant.queue_prompt_reply("30")  # order refresh time
        self.cli_mock_assistant.queue_prompt_reply("1")  # order amount
        self.cli_mock_assistant.queue_prompt_reply("No")  # ping pong feature

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.prompt_for_configuration(strategy_file_name))
        self.assertEqual(None, self.app.strategy_file_name)
        self.assertEqual(None, self.app.strategy_name)
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the connection check to complete. See logs for more details."
            ))
Exemplo n.º 4
0
class HistoryCommandTest(unittest.TestCase):
    @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher")
    def setUp(self, _: MagicMock) -> None:
        super().setUp()
        self.ev_loop = asyncio.get_event_loop()

        self.async_run_with_timeout(read_system_configs_from_yml())

        self.app = HummingbotApplication()

        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()
        self.global_config_backup = deepcopy(global_config_map)
        self.mock_strategy_name = "test-strategy"

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        self.reset_global_config()
        db_path = Path(
            SQLConnectionManager.create_db_path(
                db_name=self.mock_strategy_name))
        db_path.unlink(missing_ok=True)
        super().tearDown()

    def reset_global_config(self):
        for key, value in self.global_config_backup.items():
            global_config_map[key] = value

    @staticmethod
    def get_async_sleep_fn(delay: float):
        async def async_sleep(*_, **__):
            await asyncio.sleep(delay)

        return async_sleep

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

    def async_run_with_timeout_coroutine_must_raise_timeout(
            self, coroutine: Awaitable, timeout: float = 1):
        class DesiredError(Exception):
            pass

        async def run_coro_that_raises(coro: Awaitable):
            try:
                await coro
            except asyncio.TimeoutError:
                raise DesiredError

        try:
            self.async_run_with_timeout(run_coro_that_raises(coroutine),
                                        timeout)
        except DesiredError:  # the coroutine raised an asyncio.TimeoutError as expected
            raise asyncio.TimeoutError
        except asyncio.TimeoutError:  # the coroutine did not finish on time
            raise RuntimeError

    def get_trades(self) -> List[TradeFill]:
        trade_fee = AddedToCostTradeFee(percent=Decimal("5"))
        trades = [
            TradeFill(
                config_file_path=f"{self.mock_strategy_name}.yml",
                strategy=self.mock_strategy_name,
                market="binance",
                symbol="BTC-USDT",
                base_asset="BTC",
                quote_asset="USDT",
                timestamp=int(time.time()),
                order_id="someId",
                trade_type="BUY",
                order_type="LIMIT",
                price=1,
                amount=2,
                leverage=1,
                trade_fee=trade_fee.to_json(),
                exchange_trade_id="someExchangeId",
            )
        ]
        return trades

    @patch(
        "hummingbot.client.command.history_command.HistoryCommand.get_current_balances"
    )
    def test_history_report_raises_on_get_current_balances_network_timeout(
            self, get_current_balances_mock: AsyncMock):
        get_current_balances_mock.side_effect = self.get_async_sleep_fn(
            delay=0.02)
        global_config_map["other_commands_timeout"].value = 0.01
        trades = self.get_trades()

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.history_report(start_time=time.time(), trades=trades),
                10000)
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the balances retrieval to complete. See logs for more details."
            ))

    @patch(
        "hummingbot.client.hummingbot_application.HummingbotApplication._notify"
    )
    def test_list_trades(self, notify_mock):
        global_config_map["tables_format"].value = "psql"

        captures = []
        notify_mock.side_effect = lambda s: captures.append(s)
        self.app.strategy_file_name = f"{self.mock_strategy_name}.yml"

        trade_fee = AddedToCostTradeFee(percent=Decimal("5"))
        order_id = PaperTradeExchange.random_order_id(order_side="BUY",
                                                      trading_pair="BTC-USDT")
        with self.app.trade_fill_db.get_new_session() as session:
            o = Order(
                id=order_id,
                config_file_path=f"{self.mock_strategy_name}.yml",
                strategy=self.mock_strategy_name,
                market="binance",
                symbol="BTC-USDT",
                base_asset="BTC",
                quote_asset="USDT",
                creation_timestamp=0,
                order_type="LMT",
                amount=4,
                leverage=0,
                price=3,
                last_status="PENDING",
                last_update_timestamp=0,
            )
            session.add(o)
            for i in [1, 2]:
                t = TradeFill(
                    config_file_path=f"{self.mock_strategy_name}.yml",
                    strategy=self.mock_strategy_name,
                    market="binance",
                    symbol="BTC-USDT",
                    base_asset="BTC",
                    quote_asset="USDT",
                    timestamp=i,
                    order_id=order_id,
                    trade_type="BUY",
                    order_type="LIMIT",
                    price=i,
                    amount=2,
                    leverage=1,
                    trade_fee=trade_fee.to_json(),
                    exchange_trade_id=f"someExchangeId{i}",
                )
                session.add(t)
            session.commit()

        self.app.list_trades(start_time=0)

        self.assertEqual(1, len(captures))

        creation_time_str = str(datetime.datetime.fromtimestamp(0))

        df_str_expected = (
            f"\n  Recent trades:"
            f"\n    +---------------------+------------+----------+--------------+--------+---------+----------+------------+------------+-------+"  # noqa: E501
            f"\n    | Timestamp           | Exchange   | Market   | Order_type   | Side   |   Price |   Amount |   Leverage | Position   | Age   |"  # noqa: E501
            f"\n    |---------------------+------------+----------+--------------+--------+---------+----------+------------+------------+-------|"  # noqa: E501
            f"\n    | {creation_time_str} | binance    | BTC-USDT | limit        | buy    |       1 |        2 |          1 | NIL        | n/a   |"  # noqa: E501
            f"\n    | {creation_time_str} | binance    | BTC-USDT | limit        | buy    |       2 |        2 |          1 | NIL        | n/a   |"  # noqa: E501
            f"\n    +---------------------+------------+----------+--------------+--------+---------+----------+------------+------------+-------+"  # noqa: E501
        )

        self.assertEqual(df_str_expected, captures[0])
Exemplo n.º 5
0
class ConnectCommandTest(unittest.TestCase):
    @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher")
    def setUp(self, _: MagicMock) -> None:
        super().setUp()
        self.ev_loop = asyncio.get_event_loop()

        self.async_run_with_timeout(read_system_configs_from_yml())

        self.app = HummingbotApplication()
        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()
        self.global_config_backup = deepcopy(global_config_map)

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        self.reset_global_config()
        Security._decryption_done.clear()
        super().tearDown()

    def reset_global_config(self):
        for key, value in self.global_config_backup.items():
            global_config_map[key] = value

    @staticmethod
    def get_async_sleep_fn(delay: float):
        async def async_sleep(*_, **__):
            await asyncio.sleep(delay)

        return async_sleep

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

    def async_run_with_timeout_coroutine_must_raise_timeout(
            self, coroutine: Awaitable, timeout: float = 1):
        class DesiredError(Exception):
            pass

        async def run_coro_that_raises(coro: Awaitable):
            try:
                await coro
            except asyncio.TimeoutError:
                raise DesiredError

        try:
            self.async_run_with_timeout(run_coro_that_raises(coroutine),
                                        timeout)
        except DesiredError:  # the coroutine raised an asyncio.TimeoutError as expected
            raise asyncio.TimeoutError
        except asyncio.TimeoutError:  # the coroutine did not finish on time
            raise RuntimeError

    @patch("hummingbot.client.config.security.Security.update_secure_config")
    @patch("hummingbot.client.config.security.Security.encrypted_file_exists")
    @patch("hummingbot.client.config.security.Security.api_keys")
    @patch("hummingbot.user.user_balances.UserBalances.add_exchange")
    def test_connect_exchange_success(
        self,
        add_exchange_mock: AsyncMock,
        api_keys_mock: AsyncMock,
        encrypted_file_exists_mock: MagicMock,
        _: MagicMock,
    ):
        add_exchange_mock.return_value = None
        exchange = "binance"
        api_key = "someKey"
        api_secret = "someSecret"
        api_keys_mock.return_value = {
            "binance_api_key": api_key,
            "binance_api_secret": api_secret
        }
        encrypted_file_exists_mock.return_value = False
        global_config_map["other_commands_timeout"].value = 30
        self.cli_mock_assistant.queue_prompt_reply(api_key)  # binance API key
        self.cli_mock_assistant.queue_prompt_reply(
            api_secret)  # binance API secret

        self.async_run_with_timeout(self.app.connect_exchange(exchange))
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=f"\nYou are now connected to {exchange}."))
        self.assertFalse(self.app.placeholder_mode)
        self.assertFalse(self.app.app.hide_input)

    @patch("hummingbot.client.config.security.Security.update_secure_config")
    @patch("hummingbot.client.config.security.Security.encrypted_file_exists")
    @patch("hummingbot.client.config.security.Security.api_keys")
    @patch("hummingbot.user.user_balances.UserBalances.add_exchange")
    def test_connect_exchange_handles_network_timeouts(
        self,
        add_exchange_mock: AsyncMock,
        api_keys_mock: AsyncMock,
        encrypted_file_exists_mock: MagicMock,
        _: MagicMock,
    ):
        add_exchange_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        global_config_map["other_commands_timeout"].value = 0.01
        api_key = "someKey"
        api_secret = "someSecret"
        api_keys_mock.return_value = {
            "binance_api_key": api_key,
            "binance_api_secret": api_secret
        }
        encrypted_file_exists_mock.return_value = False
        self.cli_mock_assistant.queue_prompt_reply(api_key)  # binance API key
        self.cli_mock_assistant.queue_prompt_reply(
            api_secret)  # binance API secret

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.connect_exchange("binance"))
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the connection to complete. See logs for more details."
            ))
        self.assertFalse(self.app.placeholder_mode)
        self.assertFalse(self.app.app.hide_input)

    @patch("hummingbot.user.user_balances.UserBalances.update_exchanges")
    def test_connection_df_handles_network_timeouts(
            self, update_exchanges_mock: AsyncMock):
        update_exchanges_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        global_config_map["other_commands_timeout"].value = 0.01

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.connection_df())
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the connection table to populate. See logs for more details."
            ))

    @patch("hummingbot.user.user_balances.UserBalances.update_exchanges")
    def test_connection_df_handles_network_timeouts_logs_hidden(
            self, update_exchanges_mock: AsyncMock):
        self.cli_mock_assistant.toggle_logs()

        update_exchanges_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        global_config_map["other_commands_timeout"].value = 0.01

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.connection_df())
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the connection table to populate. See logs for more details."
            ))

    @patch(
        "hummingbot.client.hummingbot_application.HummingbotApplication._notify"
    )
    @patch(
        "hummingbot.client.hummingbot_application.HummingbotApplication.connection_df"
    )
    def test_show_connections(self, connection_df_mock, notify_mock):
        global_config_map["tables_format"].value = "psql"

        Security._decryption_done.set()

        captures = []
        notify_mock.side_effect = lambda s: captures.append(s)

        connections_df = pd.DataFrame(
            columns=pd.Index(
                ['Exchange', '  Keys Added', '  Keys Confirmed', '  Status'],
                dtype='object'),
            data=[["ascend_ex", "Yes", "Yes", "&cYELLOW"],
                  ["beaxy", "Yes", "Yes", "&cGREEN"]])
        connection_df_mock.return_value = (connections_df, [])

        self.async_run_with_timeout(self.app.show_connections())

        self.assertEqual(2, len(captures))
        self.assertEqual("\nTesting connections, please wait...", captures[0])

        df_str_expected = (
            "    +------------+----------------+--------------------+------------+"
            "\n    | Exchange   |   Keys Added   |   Keys Confirmed   |   Status   |"
            "\n    |------------+----------------+--------------------+------------|"
            "\n    | ascend_ex  | Yes            | Yes                | &cYELLOW   |"
            "\n    | beaxy      | Yes            | Yes                | &cGREEN    |"
            "\n    +------------+----------------+--------------------+------------+"
        )

        self.assertEqual(df_str_expected, captures[1])
Exemplo n.º 6
0
class ConnectCommandTest(unittest.TestCase):
    @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher")
    def setUp(self, _: MagicMock) -> None:
        super().setUp()
        self.ev_loop = asyncio.get_event_loop()

        self.async_run_with_timeout(read_system_configs_from_yml())

        self.app = HummingbotApplication()
        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()
        self.global_config_backup = deepcopy(global_config_map)

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        self.reset_global_config()
        super().tearDown()

    def reset_global_config(self):
        for key, value in self.global_config_backup.items():
            global_config_map[key] = value

    @staticmethod
    def get_async_sleep_fn(delay: float):
        async def async_sleep(*_, **__):
            await asyncio.sleep(delay)

        return async_sleep

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

    def async_run_with_timeout_coroutine_must_raise_timeout(
            self, coroutine: Awaitable, timeout: float = 1):
        class DesiredError(Exception):
            pass

        async def run_coro_that_raises(coro: Awaitable):
            try:
                await coro
            except asyncio.TimeoutError:
                raise DesiredError

        try:
            self.async_run_with_timeout(run_coro_that_raises(coroutine),
                                        timeout)
        except DesiredError:  # the coroutine raised an asyncio.TimeoutError as expected
            raise asyncio.TimeoutError
        except asyncio.TimeoutError:  # the coroutine did not finish on time
            raise RuntimeError

    @patch("hummingbot.client.config.security.Security.update_secure_config")
    @patch("hummingbot.client.config.security.Security.encrypted_file_exists")
    @patch("hummingbot.client.config.security.Security.api_keys")
    @patch("hummingbot.user.user_balances.UserBalances.add_exchange")
    def test_connect_exchange_success(
        self,
        add_exchange_mock: AsyncMock,
        api_keys_mock: AsyncMock,
        encrypted_file_exists_mock: MagicMock,
        _: MagicMock,
    ):
        add_exchange_mock.return_value = None
        exchange = "binance"
        api_key = "someKey"
        api_secret = "someSecret"
        api_keys_mock.return_value = {
            "binance_api_key": api_key,
            "binance_api_secret": api_secret
        }
        encrypted_file_exists_mock.return_value = False
        global_config_map["other_commands_timeout"].value = 30
        self.cli_mock_assistant.queue_prompt_reply(api_key)  # binance API key
        self.cli_mock_assistant.queue_prompt_reply(
            api_secret)  # binance API secret

        self.async_run_with_timeout(self.app.connect_exchange(exchange))
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=f"\nYou are now connected to {exchange}."))
        self.assertFalse(self.app.placeholder_mode)
        self.assertFalse(self.app.app.hide_input)

    @patch("hummingbot.client.config.security.Security.update_secure_config")
    @patch("hummingbot.client.config.security.Security.encrypted_file_exists")
    @patch("hummingbot.client.config.security.Security.api_keys")
    @patch("hummingbot.user.user_balances.UserBalances.add_exchange")
    def test_connect_exchange_handles_network_timeouts(
        self,
        add_exchange_mock: AsyncMock,
        api_keys_mock: AsyncMock,
        encrypted_file_exists_mock: MagicMock,
        _: MagicMock,
    ):
        add_exchange_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        global_config_map["other_commands_timeout"].value = 0.01
        api_key = "someKey"
        api_secret = "someSecret"
        api_keys_mock.return_value = {
            "binance_api_key": api_key,
            "binance_api_secret": api_secret
        }
        encrypted_file_exists_mock.return_value = False
        self.cli_mock_assistant.queue_prompt_reply(api_key)  # binance API key
        self.cli_mock_assistant.queue_prompt_reply(
            api_secret)  # binance API secret

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.connect_exchange("binance"))
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the connection to complete. See logs for more details."
            ))
        self.assertFalse(self.app.placeholder_mode)
        self.assertFalse(self.app.app.hide_input)

    @patch("hummingbot.user.user_balances.UserBalances.update_exchanges")
    def test_connection_df_handles_network_timeouts(
            self, update_exchanges_mock: AsyncMock):
        update_exchanges_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        global_config_map["other_commands_timeout"].value = 0.01

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.connection_df())
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the connection table to populate. See logs for more details."
            ))

    @patch("hummingbot.user.user_balances.UserBalances.update_exchanges")
    def test_connection_df_handles_network_timeouts_logs_hidden(
            self, update_exchanges_mock: AsyncMock):
        self.cli_mock_assistant.toggle_logs()

        update_exchanges_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        global_config_map["other_commands_timeout"].value = 0.01

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.connection_df())
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the connection table to populate. See logs for more details."
            ))
class ImportCommandTest(unittest.TestCase):
    @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher")
    def setUp(self, _: MagicMock) -> None:
        super().setUp()
        self.ev_loop = asyncio.get_event_loop()

        self.async_run_with_timeout(read_system_configs_from_yml())

        self.app = HummingbotApplication()
        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        super().tearDown()

    @staticmethod
    async def raise_timeout(*args, **kwargs):
        raise asyncio.TimeoutError

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

    def async_run_with_timeout_coroutine_must_raise_timeout(
            self, coroutine: Awaitable, timeout: float = 1):
        class DesiredError(Exception):
            pass

        async def run_coro_that_raises(coro: Awaitable):
            try:
                await coro
            except asyncio.TimeoutError:
                raise DesiredError

        try:
            self.async_run_with_timeout(run_coro_that_raises(coroutine),
                                        timeout)
        except DesiredError:  # the coroutine raised an asyncio.TimeoutError as expected
            raise asyncio.TimeoutError
        except asyncio.TimeoutError:  # the coroutine did not finish on time
            raise RuntimeError

    @staticmethod
    def build_dummy_strategy_config_cls(
            strategy_name: str) -> Type[BaseClientModel]:
        class SomeEnum(ClientConfigEnum):
            ONE = "one"

        class DoubleNestedModel(BaseClientModel):
            double_nested_attr: datetime = Field(
                default=datetime(2022, 1, 1, 10, 30),
                description="Double nested attr description")

        class NestedModel(BaseClientModel):
            nested_attr: str = Field(
                default="some value",
                description="Nested attr\nmultiline description",
            )
            double_nested_model: DoubleNestedModel = Field(
                default=DoubleNestedModel(), )

        class DummyModel(BaseTradingStrategyConfigMap):
            strategy: str = strategy_name
            exchange: str = "binance"
            market: str = "BTC-USDT"
            some_attr: SomeEnum = Field(
                default=SomeEnum.ONE,
                description="Some description",
            )
            nested_model: NestedModel = Field(
                default=NestedModel(),
                description="Nested model description",
            )
            another_attr: Decimal = Field(
                default=Decimal("1.0"),
                description="Some other\nmultiline description",
            )
            non_nested_no_description: time = Field(default=time(10, 30), )
            date_attr: date = Field(default=date(2022, 1, 2))
            no_default: str = Field(default=...)

            class Config:
                title = "dummy_model"

        return DummyModel

    @patch(
        "hummingbot.client.command.import_command.load_strategy_config_map_from_file"
    )
    @patch(
        "hummingbot.client.command.status_command.StatusCommand.status_check_all"
    )
    def test_import_config_file_success_legacy(
            self, status_check_all_mock: AsyncMock,
            load_strategy_config_map_from_file: AsyncMock):
        strategy_name = "some_strategy"
        strategy_file_name = f"{strategy_name}.yml"
        status_check_all_mock.return_value = True
        strategy_conf_var = ConfigVar("strategy", None)
        strategy_conf_var.value = strategy_name
        load_strategy_config_map_from_file.return_value = {
            "strategy": strategy_conf_var
        }

        self.async_run_with_timeout(
            self.app.import_config_file(strategy_file_name))
        self.assertEqual(strategy_file_name, self.app.strategy_file_name)
        self.assertEqual(strategy_name, self.app.strategy_name)
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                "\nEnter \"start\" to start market making."))

    @patch(
        "hummingbot.client.command.import_command.load_strategy_config_map_from_file"
    )
    @patch(
        "hummingbot.client.command.status_command.StatusCommand.status_check_all"
    )
    def test_import_config_file_handles_network_timeouts_legacy(
            self, status_check_all_mock: AsyncMock,
            load_strategy_config_map_from_file: AsyncMock):
        strategy_name = "some_strategy"
        strategy_file_name = f"{strategy_name}.yml"
        status_check_all_mock.side_effect = self.raise_timeout
        strategy_conf_var = ConfigVar("strategy", None)
        strategy_conf_var.value = strategy_name
        load_strategy_config_map_from_file.return_value = {
            "strategy": strategy_conf_var
        }

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.import_config_file(strategy_file_name))
        self.assertEqual(None, self.app.strategy_file_name)
        self.assertEqual(None, self.app.strategy_name)

    @patch(
        "hummingbot.client.config.config_helpers.get_strategy_pydantic_config_cls"
    )
    @patch(
        "hummingbot.client.command.status_command.StatusCommand.status_check_all"
    )
    def test_import_config_file_success(
            self, status_check_all_mock: AsyncMock,
            get_strategy_pydantic_config_cls: MagicMock):
        strategy_name = "perpetual_market_making"
        strategy_file_name = f"{strategy_name}.yml"
        status_check_all_mock.return_value = True
        dummy_strategy_config_cls = self.build_dummy_strategy_config_cls(
            strategy_name)
        get_strategy_pydantic_config_cls.return_value = dummy_strategy_config_cls
        cm = ClientConfigAdapter(
            dummy_strategy_config_cls(no_default="some value"))

        with TemporaryDirectory() as d:
            d = Path(d)
            import_command.STRATEGIES_CONF_DIR_PATH = d
            temp_file_name = d / strategy_file_name
            save_to_yml(temp_file_name, cm)
            self.async_run_with_timeout(
                self.app.import_config_file(strategy_file_name))

        self.assertEqual(strategy_file_name, self.app.strategy_file_name)
        self.assertEqual(strategy_name, self.app.strategy_name)
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                "\nEnter \"start\" to start market making."))
        self.assertEqual(cm, self.app.strategy_config_map)

    @patch(
        "hummingbot.client.config.config_helpers.get_strategy_pydantic_config_cls"
    )
    @patch(
        "hummingbot.client.command.status_command.StatusCommand.status_check_all"
    )
    def test_import_incomplete_config_file_success(
            self, status_check_all_mock: AsyncMock,
            get_strategy_pydantic_config_cls: MagicMock):
        strategy_name = "perpetual_market_making"
        strategy_file_name = f"{strategy_name}.yml"
        status_check_all_mock.return_value = True
        dummy_strategy_config_cls = self.build_dummy_strategy_config_cls(
            strategy_name)
        get_strategy_pydantic_config_cls.return_value = dummy_strategy_config_cls
        cm = ClientConfigAdapter(
            dummy_strategy_config_cls(no_default="some value"))

        with TemporaryDirectory() as d:
            d = Path(d)
            import_command.STRATEGIES_CONF_DIR_PATH = d
            temp_file_name = d / strategy_file_name
            cm_yml_str = cm.generate_yml_output_str_with_comments()
            cm_yml_str = cm_yml_str.replace("\nno_default: some value\n", "")
            with open(temp_file_name, "w+") as outfile:
                outfile.write(cm_yml_str)
            self.async_run_with_timeout(
                self.app.import_config_file(strategy_file_name))

        self.assertEqual(strategy_file_name, self.app.strategy_file_name)
        self.assertEqual(strategy_name, self.app.strategy_name)
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                "\nEnter \"start\" to start market making."))
        self.assertNotEqual(cm, self.app.strategy_config_map)

        validation_errors = self.app.strategy_config_map.validate_model()

        self.assertEqual(1, len(validation_errors))
        self.assertEqual("no_default - field required", validation_errors[0])
Exemplo n.º 8
0
class HistoryCommandTest(unittest.TestCase):
    @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher")
    def setUp(self, _: MagicMock) -> None:
        super().setUp()
        self.app = HummingbotApplication()
        self.ev_loop = asyncio.get_event_loop()
        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()
        self.global_config_backup = deepcopy(global_config_map)

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        self.reset_global_config()
        super().tearDown()

    def reset_global_config(self):
        for key, value in self.global_config_backup.items():
            global_config_map[key] = value

    @staticmethod
    def get_async_sleep_fn(delay: float):
        async def async_sleep(*_, **__):
            await asyncio.sleep(delay)
        return async_sleep

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

    def async_run_with_timeout_coroutine_must_raise_timeout(self, coroutine: Awaitable, timeout: float = 1):
        class DesiredError(Exception):
            pass

        async def run_coro_that_raises(coro: Awaitable):
            try:
                await coro
            except asyncio.TimeoutError:
                raise DesiredError

        try:
            self.async_run_with_timeout(run_coro_that_raises(coroutine), timeout)
        except DesiredError:  # the coroutine raised an asyncio.TimeoutError as expected
            raise asyncio.TimeoutError
        except asyncio.TimeoutError:  # the coroutine did not finish on time
            raise RuntimeError

    @staticmethod
    def get_trades() -> List[TradeFill]:
        trade_fee = TradeFee(percent=Decimal("5"))
        trades = [
            TradeFill(
                id=1,
                config_file_path="some-strategy.yml",
                strategy="pure_market_making",
                market="binance",
                symbol="BTC-USDT",
                base_asset="BTC",
                quote_asset="USDT",
                timestamp=int(time.time()),
                order_id="someId",
                trade_type="BUY",
                order_type="LIMIT",
                price=1,
                amount=2,
                leverage=1,
                trade_fee=TradeFee.to_json(trade_fee),
                exchange_trade_id="someExchangeId",
            )
        ]
        return trades

    @patch("hummingbot.client.command.history_command.HistoryCommand.get_current_balances")
    def test_history_report_raises_on_get_current_balances_network_timeout(
        self, get_current_balances_mock: AsyncMock
    ):
        get_current_balances_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        global_config_map["other_commands_timeout"].value = 0.01
        trades = self.get_trades()

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.history_report(start_time=time.time(), trades=trades), 10000
            )
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg="\nA network error prevented the balances retrieval to complete. See logs for more details."
            )
        )
class BalanceCommandTest(unittest.TestCase):
    @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher")
    def setUp(self, _: MagicMock) -> None:
        super().setUp()
        self.ev_loop = asyncio.get_event_loop()

        self.async_run_with_timeout(read_system_configs_from_yml())

        self.app = HummingbotApplication()
        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        super().tearDown()

    @staticmethod
    def get_async_sleep_fn(delay: float):
        async def async_sleep(*_, **__):
            await asyncio.sleep(delay)

        return async_sleep

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

    def async_run_with_timeout_coroutine_must_raise_timeout(
            self, coroutine: Awaitable, timeout: float = 1):
        class DesiredError(Exception):
            pass

        async def run_coro_that_raises(coro: Awaitable):
            try:
                await coro
            except asyncio.TimeoutError:
                raise DesiredError

        try:
            self.async_run_with_timeout(run_coro_that_raises(coroutine),
                                        timeout)
        except DesiredError:  # the coroutine raised an asyncio.TimeoutError as expected
            raise asyncio.TimeoutError
        except asyncio.TimeoutError:  # the coroutine did not finish on time
            raise RuntimeError

    @patch(
        "hummingbot.user.user_balances.UserBalances.all_balances_all_exchanges"
    )
    def test_show_balances_handles_network_timeouts(
            self, all_balances_all_exchanges_mock):
        all_balances_all_exchanges_mock.side_effect = self.get_async_sleep_fn(
            delay=0.02)
        self.app.client_config_map.commands_timeout.other_commands_timeout = Decimal(
            "0.01")

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(
                self.app.show_balances())
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg=
                "\nA network error prevented the balances to update. See logs for more details."
            ))
Exemplo n.º 10
0
class StatusCommandTest(unittest.TestCase):
    @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher")
    def setUp(self, _: MagicMock) -> None:
        super().setUp()
        self.ev_loop = asyncio.get_event_loop()
        self.app = HummingbotApplication()
        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()
        self.global_config_backup = deepcopy(global_config_map)

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        self.reset_global_config()
        super().tearDown()

    def reset_global_config(self):
        for key, value in self.global_config_backup.items():
            global_config_map[key] = value

    @staticmethod
    def get_async_sleep_fn(delay: float):
        async def async_sleep(*_, **__):
            await asyncio.sleep(delay)
        return async_sleep

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

    def async_run_with_timeout_coroutine_must_raise_timeout(self, coroutine: Awaitable, timeout: float = 1):
        class DesiredError(Exception):
            pass

        async def run_coro_that_raises(coro: Awaitable):
            try:
                await coro
            except asyncio.TimeoutError:
                raise DesiredError

        try:
            self.async_run_with_timeout(run_coro_that_raises(coroutine), timeout)
        except DesiredError:  # the coroutine raised an asyncio.TimeoutError as expected
            raise asyncio.TimeoutError
        except asyncio.TimeoutError:  # the coroutine did not finish on time
            raise RuntimeError

    @patch("hummingbot.client.command.status_command.StatusCommand.validate_required_connections")
    @patch("hummingbot.client.config.security.Security.is_decryption_done")
    def test_status_check_all_handles_network_timeouts(
        self, is_decryption_done_mock, validate_required_connections_mock
    ):
        validate_required_connections_mock.side_effect = self.get_async_sleep_fn(delay=0.02)
        global_config_map["other_commands_timeout"].value = 0.01
        is_decryption_done_mock.return_value = True
        strategy_name = "some-strategy"
        self.app.strategy_name = strategy_name
        self.app.strategy_file_name = f"{strategy_name}.yml"

        with self.assertRaises(asyncio.TimeoutError):
            self.async_run_with_timeout_coroutine_must_raise_timeout(self.app.status_check_all())
        self.assertTrue(
            self.cli_mock_assistant.check_log_called_with(
                msg="\nA network error prevented the connection check to complete. See logs for more details."
            )
        )
Exemplo n.º 11
0
class ConfigCommandTest(unittest.TestCase):
    @patch("hummingbot.core.utils.trading_pair_fetcher.TradingPairFetcher")
    def setUp(self, _: MagicMock) -> None:
        super().setUp()
        self.ev_loop = asyncio.get_event_loop()

        self.async_run_with_timeout(read_system_configs_from_yml())

        self.client_config = ClientConfigMap()
        self.config_adapter = ClientConfigAdapter(self.client_config)

        self.app = HummingbotApplication(client_config_map=self.config_adapter)
        self.cli_mock_assistant = CLIMockingAssistant(self.app.app)
        self.cli_mock_assistant.start()

    def tearDown(self) -> None:
        self.cli_mock_assistant.stop()
        super().tearDown()

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

    @patch("hummingbot.client.hummingbot_application.get_strategy_config_map")
    @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify")
    def test_list_configs(self, notify_mock, get_strategy_config_map_mock):
        captures = []
        notify_mock.side_effect = lambda s: captures.append(s)
        strategy_name = "some-strategy"
        self.app.strategy_name = strategy_name

        strategy_config_map_mock = {
            "five": ConfigVar(key="five", prompt=""),
            "six": ConfigVar(key="six", prompt="", default="sixth"),
        }
        strategy_config_map_mock["five"].value = "fifth"
        strategy_config_map_mock["six"].value = "sixth"
        get_strategy_config_map_mock.return_value = strategy_config_map_mock

        self.app.list_configs()

        self.assertEqual(6, len(captures))
        self.assertEqual("\nGlobal Configurations:", captures[0])

        df_str_expected = ("    +--------------------------+----------------------+\n"
                           "    | Key                      | Value                |\n"
                           "    |--------------------------+----------------------|\n"
                           "    | kill_switch_mode         | kill_switch_disabled |\n"
                           "    | autofill_import          | disabled             |\n"
                           "    | telegram_mode            | telegram_disabled    |\n"
                           "    | send_error_logs          | True                 |\n"
                           "    | pmm_script_mode          | pmm_script_disabled  |\n"
                           "    | gateway                  |                      |\n"
                           "    | ∟ gateway_api_host       | localhost            |\n"
                           "    | ∟ gateway_api_port       | 5000                 |\n"
                           "    | rate_oracle_source       | binance              |\n"
                           "    | global_token             |                      |\n"
                           "    | ∟ global_token_symbol    | $                    |\n"
                           "    | rate_limits_share_pct    | 100                  |\n"
                           "    | commands_timeout         |                      |\n"
                           "    | ∟ create_command_timeout | 10                   |\n"
                           "    | ∟ other_commands_timeout | 30                   |\n"
                           "    | tables_format            | psql                 |\n"
                           "    +--------------------------+----------------------+")

        self.assertEqual(df_str_expected, captures[1])
        self.assertEqual("\nColor Settings:", captures[2])

        df_str_expected = ("    +--------------------+---------+\n"
                           "    | Key                | Value   |\n"
                           "    |--------------------+---------|\n"
                           "    | ∟ top_pane         | #000000 |\n"
                           "    | ∟ bottom_pane      | #000000 |\n"
                           "    | ∟ output_pane      | #262626 |\n"
                           "    | ∟ input_pane       | #1C1C1C |\n"
                           "    | ∟ logs_pane        | #121212 |\n"
                           "    | ∟ terminal_primary | #5FFFD7 |\n"
                           "    +--------------------+---------+")

        self.assertEqual(df_str_expected, captures[3])
        self.assertEqual("\nStrategy Configurations:", captures[4])

        df_str_expected = (
            "    +-------+---------+"
            "\n    | Key   | Value   |"
            "\n    |-------+---------|"
            "\n    | five  | fifth   |"
            "\n    | six   | sixth   |"
            "\n    +-------+---------+"
        )

        self.assertEqual(df_str_expected, captures[5])

    @patch("hummingbot.client.hummingbot_application.get_strategy_config_map")
    @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify")
    def test_list_configs_pydantic_model(self, notify_mock, get_strategy_config_map_mock):
        captures = []
        notify_mock.side_effect = lambda s: captures.append(s)
        strategy_name = "some-strategy"
        self.app.strategy_name = strategy_name

        class DoubleNestedModel(BaseClientModel):
            double_nested_attr: float = Field(default=3.0)

            class Config:
                title = "double_nested_model"

        class NestedModelOne(BaseClientModel):
            nested_attr: str = Field(default="some value")
            double_nested_model: DoubleNestedModel = Field(default=DoubleNestedModel())

            class Config:
                title = "nested_mode_one"

        class NestedModelTwo(BaseClientModel):
            class Config:
                title = "nested_mode_two"

        class DummyModel(BaseClientModel):
            some_attr: int = Field(default=1)
            nested_model: Union[NestedModelTwo, NestedModelOne] = Field(default=NestedModelOne())
            another_attr: Decimal = Field(default=Decimal("1.0"))
            missing_no_default: int = Field(default=...)

            class Config:
                title = "dummy_model"

        get_strategy_config_map_mock.return_value = ClientConfigAdapter(DummyModel.construct())

        self.app.list_configs()

        self.assertEqual(6, len(captures))

        self.assertEqual("\nStrategy Configurations:", captures[4])

        df_str_expected = (
            "    +------------------------+------------------------+"
            "\n    | Key                    | Value                  |"
            "\n    |------------------------+------------------------|"
            "\n    | some_attr              | 1                      |"
            "\n    | nested_model           | nested_mode_one        |"
            "\n    | ∟ nested_attr          | some value             |"
            "\n    | ∟ double_nested_model  |                        |"
            "\n    |   ∟ double_nested_attr | 3.0                    |"
            "\n    | another_attr           | 1.0                    |"
            "\n    | missing_no_default     | &cMISSING_AND_REQUIRED |"
            "\n    +------------------------+------------------------+"
        )

        self.assertEqual(df_str_expected, captures[5])

    @patch("hummingbot.client.hummingbot_application.get_strategy_config_map")
    @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify")
    def test_config_non_configurable_key_fails(self, notify_mock, get_strategy_config_map_mock):
        class DummyModel(BaseStrategyConfigMap):
            strategy: str = Field(default="pure_market_making", client_data=None)
            some_attr: int = Field(default=1, client_data=ClientFieldData(prompt=lambda mi: "some prompt"))
            another_attr: Decimal = Field(default=Decimal("1.0"))

            class Config:
                title = "dummy_model"

        strategy_name = "some-strategy"
        self.app.strategy_name = strategy_name
        get_strategy_config_map_mock.return_value = ClientConfigAdapter(DummyModel.construct())
        self.app.config(key="some_attr")

        notify_mock.assert_not_called()

        self.app.config(key="another_attr")

        notify_mock.assert_called_once_with("Invalid key, please choose from the list.")

        notify_mock.reset_mock()
        self.app.config(key="some_key")

        notify_mock.assert_called_once_with("Invalid key, please choose from the list.")

    @patch("hummingbot.client.command.config_command.save_to_yml")
    @patch("hummingbot.client.hummingbot_application.get_strategy_config_map")
    @patch("hummingbot.client.hummingbot_application.HummingbotApplication.notify")
    def test_config_single_keys(self, _, get_strategy_config_map_mock, save_to_yml_mock):
        class NestedModel(BaseClientModel):
            nested_attr: str = Field(
                default="some value", client_data=ClientFieldData(prompt=lambda mi: "some prompt")
            )

            class Config:
                title = "nested_model"

        class DummyModel(BaseStrategyConfigMap):
            strategy: str = Field(default="pure_market_making", client_data=None)
            some_attr: int = Field(default=1, client_data=ClientFieldData(prompt=lambda mi: "some prompt"))
            nested_model: NestedModel = Field(default=NestedModel())

            class Config:
                title = "dummy_model"

        strategy_name = "some-strategy"
        self.app.strategy_name = strategy_name
        self.app.strategy_file_name = f"{strategy_name}.yml"
        config_map = ClientConfigAdapter(DummyModel.construct())
        get_strategy_config_map_mock.return_value = config_map

        self.async_run_with_timeout(self.app._config_single_key(key="some_attr", input_value=2))

        self.assertEqual(2, config_map.some_attr)
        save_to_yml_mock.assert_called_once()

        save_to_yml_mock.reset_mock()
        self.cli_mock_assistant.queue_prompt_reply("3")
        self.async_run_with_timeout(self.app._config_single_key(key="some_attr", input_value=None))

        self.assertEqual(3, config_map.some_attr)
        save_to_yml_mock.assert_called_once()

        save_to_yml_mock.reset_mock()
        self.cli_mock_assistant.queue_prompt_reply("another value")
        self.async_run_with_timeout(self.app._config_single_key(key="nested_model.nested_attr", input_value=None))

        self.assertEqual("another value", config_map.nested_model.nested_attr)
        save_to_yml_mock.assert_called_once()