diff --git a/Makefile b/Makefile index 25913c444f9..b69b0949b11 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,8 @@ test: --exclude-dir="test/connector" \ --exclude-dir="test/debug" \ --exclude-dir="test/mock" \ - --exclude-dir="test/hummingbot/connector/gateway/amm" + --exclude-dir="test/hummingbot/connector/gateway/amm" \ + --exclude-dir="test/hummingbot/strategy/uniswap_v3_lp" run_coverage: test coverage report diff --git a/bin/hummingbot.py b/bin/hummingbot.py index 7613f4a283a..6360af0b71e 100755 --- a/bin/hummingbot.py +++ b/bin/hummingbot.py @@ -78,7 +78,13 @@ async def main_async(client_config_map: ClientConfigAdapter): def main(): chdir_to_data_directory() secrets_manager_cls = ETHKeyFileSecretManger - ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + + try: + ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + except Exception: + ev_loop: asyncio.AbstractEventLoop = asyncio.new_event_loop() + asyncio.set_event_loop(ev_loop) + client_config_map = load_client_config_map_from_file() if login_prompt(secrets_manager_cls, style=load_style(client_config_map)): ev_loop.run_until_complete(main_async(client_config_map)) diff --git a/bin/hummingbot_quickstart.py b/bin/hummingbot_quickstart.py index 41a80210248..9bb78f43f71 100755 --- a/bin/hummingbot_quickstart.py +++ b/bin/hummingbot_quickstart.py @@ -148,7 +148,13 @@ def main(): else: secrets_manager = secrets_manager_cls(args.config_password) - asyncio.get_event_loop().run_until_complete(quick_start(args, secrets_manager)) + try: + ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + except Exception: + ev_loop: asyncio.AbstractEventLoop = asyncio.new_event_loop() + asyncio.set_event_loop(ev_loop) + + ev_loop.run_until_complete(quick_start(args, secrets_manager)) if __name__ == "__main__": diff --git a/conf/__init__.py b/conf/__init__.py index 886cacbb34c..1478acc35dc 100644 --- a/conf/__init__.py +++ b/conf/__init__.py @@ -32,8 +32,6 @@ "db": "**REMOVED***", } -kafka_bootstrap_server = "***REMOVED***" - # whether to enable api mocking in unit test cases mock_api_enabled = os.getenv("MOCK_API_ENABLED") @@ -120,8 +118,3 @@ coinalpha_order_book_api_username = "***REMOVED***" coinalpha_order_book_api_password = "***REMOVED***" """ - -kafka_2 = { - "bootstrap_servers": "***REMOVED***", - "zookeeper_servers": "***REMOVED***" -} diff --git a/hummingbot/client/command/status_command.py b/hummingbot/client/command/status_command.py index 80cc3d2b8e3..ddb9f6b8fd2 100644 --- a/hummingbot/client/command/status_command.py +++ b/hummingbot/client/command/status_command.py @@ -1,4 +1,5 @@ import asyncio +import threading import time from collections import OrderedDict, deque from typing import TYPE_CHECKING, Dict, List @@ -121,6 +122,10 @@ def validate_configs( def status(self, # type: HummingbotApplication live: bool = False): + if threading.current_thread() != threading.main_thread(): + self.ev_loop.call_soon_threadsafe(self.status, live) + return + safe_ensure_future(self.status_check_all(live=live), loop=self.ev_loop) async def status_check_all(self, # type: HummingbotApplication diff --git a/hummingbot/client/config/config_crypt.py b/hummingbot/client/config/config_crypt.py index d8cc84ac10f..28acd30360d 100644 --- a/hummingbot/client/config/config_crypt.py +++ b/hummingbot/client/config/config_crypt.py @@ -140,4 +140,5 @@ def _create_v3_keyfile_json(message_to_encrypt, password, kdf="pbkdf2", work_fac 'mac': encode_hex_no_prefix(mac), }, 'version': 3, + 'alias': '', # Add this line to include the 'alias' field with an empty string value } diff --git a/hummingbot/connector/exchange/foxbit/foxbit_api_user_stream_data_source.py b/hummingbot/connector/exchange/foxbit/foxbit_api_user_stream_data_source.py index 422749fb5d3..38583f9d9a7 100644 --- a/hummingbot/connector/exchange/foxbit/foxbit_api_user_stream_data_source.py +++ b/hummingbot/connector/exchange/foxbit/foxbit_api_user_stream_data_source.py @@ -18,6 +18,7 @@ class FoxbitAPIUserStreamDataSource(UserStreamTrackerDataSource): + _INTERVAL_SLEEP_INTERRUPTION = 5.0 _logger: Optional[HummingbotLogger] = None @@ -67,8 +68,9 @@ async def _connected_websocket_assistant(self) -> WSAssistant: if is_authenticated: return ws else: - self.logger().info("Some issue happens when try to subscribe at Foxbit User Stream Data, check your credentials.") - raise + error_message = "Some issue happens when try to subscribe at Foxbit User Stream Data, check your credentials." + self.logger().info(error_message) + raise Exception(error_message) except Exception as ex: self.logger().error( @@ -120,4 +122,4 @@ async def _subscribe_channels(self, async def _on_user_stream_interruption(self, websocket_assistant: Optional[WSAssistant]): await super()._on_user_stream_interruption(websocket_assistant=websocket_assistant) - await self._sleep(5) + await self._sleep(self._INTERVAL_SLEEP_INTERRUPTION) diff --git a/hummingbot/connector/exchange/kraken/kraken_api_order_book_data_source.py b/hummingbot/connector/exchange/kraken/kraken_api_order_book_data_source.py index 7c165667fd8..efcf1adab55 100755 --- a/hummingbot/connector/exchange/kraken/kraken_api_order_book_data_source.py +++ b/hummingbot/connector/exchange/kraken/kraken_api_order_book_data_source.py @@ -157,7 +157,7 @@ async def fetch_trading_pairs(cls, throttler: Optional[AsyncThrottler] = None) - if response.status == 200: data: Dict[str, Any] = await response.json() - raw_pairs = data.get("result", []) + raw_pairs = data.get("result", {}) converted_pairs: List[str] = [] for pair, details in raw_pairs.items(): if "." not in pair: diff --git a/hummingbot/connector/exchange/loopring/loopring_order_book.pyx b/hummingbot/connector/exchange/loopring/loopring_order_book.pyx index 48d06321368..512e0c859bd 100644 --- a/hummingbot/connector/exchange/loopring/loopring_order_book.pyx +++ b/hummingbot/connector/exchange/loopring/loopring_order_book.pyx @@ -6,7 +6,6 @@ from typing import ( ) import ujson -from aiokafka import ConsumerRecord from hummingbot.connector.exchange.loopring.loopring_order_book_message import LoopringOrderBookMessage from hummingbot.core.data_type.common import TradeType @@ -58,16 +57,6 @@ cdef class LoopringOrderBook(OrderBook): "amount": msg[3] }, timestamp=ts * 1e-3) - @classmethod - def snapshot_message_from_kafka(cls, record: ConsumerRecord, metadata: Optional[Dict] = None) -> OrderBookMessage: - msg = ujson.loads(record.value.decode()) - return LoopringOrderBookMessage(OrderBookMessageType.SNAPSHOT, msg, timestamp=record.timestamp * 1e-3) - - @classmethod - def diff_message_from_kafka(cls, record: ConsumerRecord, metadata: Optional[Dict] = None) -> OrderBookMessage: - msg = ujson.loads(record.value.decode()) - return LoopringOrderBookMessage(OrderBookMessageType.DIFF, msg) - @classmethod def from_snapshot(cls, snapshot: OrderBookMessage): raise NotImplementedError("loopring order book needs to retain individual order data.") diff --git a/hummingbot/core/data_type/order_book.pyx b/hummingbot/core/data_type/order_book.pyx index 66edb5c50ba..8142e432913 100644 --- a/hummingbot/core/data_type/order_book.pyx +++ b/hummingbot/core/data_type/order_book.pyx @@ -13,7 +13,6 @@ from typing import ( import numpy as np import pandas as pd -from aiokafka import ConsumerRecord from cython.operator cimport( address as ref, @@ -476,14 +475,6 @@ cdef class OrderBook(PubSub): def get_quote_volume_for_price(self, is_buy: bool, price: float) -> OrderBookQueryResult: return self.c_get_quote_volume_for_price(is_buy, price) - @classmethod - def snapshot_message_from_kafka(cls, record: ConsumerRecord, metadata: Optional[Dict] = None) -> OrderBookMessage: - pass - - @classmethod - def diff_message_from_kafka(cls, record: ConsumerRecord, metadata: Optional[Dict] = None) -> OrderBookMessage: - pass - def restore_from_snapshot_and_diffs(self, snapshot: OrderBookMessage, diffs: List[OrderBookMessage]): replay_position = bisect.bisect_right(diffs, snapshot) replay_diffs = diffs[replay_position:] diff --git a/hummingbot/core/utils/async_call_scheduler.py b/hummingbot/core/utils/async_call_scheduler.py index 54e3fc5379a..57a105262f4 100644 --- a/hummingbot/core/utils/async_call_scheduler.py +++ b/hummingbot/core/utils/async_call_scheduler.py @@ -1,18 +1,14 @@ #!/usr/bin/env python import asyncio -from async_timeout import timeout import logging -from typing import ( - Optional, - Coroutine, - NamedTuple, - Callable -) +from typing import Callable, Coroutine, NamedTuple, Optional + +from async_timeout import timeout import hummingbot -from hummingbot.logger import HummingbotLogger from hummingbot.core.utils.async_utils import safe_ensure_future +from hummingbot.logger import HummingbotLogger class AsyncCallSchedulerItem(NamedTuple): @@ -42,7 +38,7 @@ def __init__(self, call_interval: float = 0.01): self._coro_queue: asyncio.Queue = asyncio.Queue() self._coro_scheduler_task: Optional[asyncio.Task] = None self._call_interval: float = call_interval - self._ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + self.reset_event_loop() @property def coro_queue(self) -> asyncio.Queue: @@ -56,6 +52,9 @@ def coro_scheduler_task(self) -> Optional[asyncio.Task]: def started(self) -> bool: return self._coro_scheduler_task is not None + def reset_event_loop(self): + self._ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() + def start(self): if self._coro_scheduler_task is not None: self.stop() diff --git a/hummingbot/remote_iface/mqtt.py b/hummingbot/remote_iface/mqtt.py index 6c7eef54876..e218ffc8b98 100644 --- a/hummingbot/remote_iface/mqtt.py +++ b/hummingbot/remote_iface/mqtt.py @@ -215,7 +215,11 @@ def _on_cmd_config(self, msg: ConfigCommandMessage.Request): invalid_params = [] for param in msg.params: if param[0] in self._hb_app.configurable_keys(): - self._hb_app.config(param[0], param[1]) + self._ev_loop.call_soon_threadsafe( + self._hb_app.config, + param[0], + param[1] + ) response.changes.append((param[0], param[1])) else: invalid_params.append(param[0]) @@ -285,7 +289,9 @@ def _on_cmd_status(self, msg: StatusCommandMessage.Request): response.msg = 'No strategy is currently running!' return response if msg.async_backend: - self._hb_app.status() + self._ev_loop.call_soon_threadsafe( + self._hb_app.status + ) else: res = call_sync( self._hb_app.strategy_status(), @@ -849,6 +855,8 @@ def start(self, with_health: bool = True) -> None: def stop(self, with_health: bool = True): self.broadcast_status_update("offline", msg_type="availability") super().stop() + if self._hb_thread: + self._hb_thread.stop() self._remove_status_updates() self._remove_notifier() self._remove_log_handlers() diff --git a/setup.py b/setup.py index e1849e93929..3119c4c951f 100644 --- a/setup.py +++ b/setup.py @@ -47,16 +47,18 @@ def main(): "0x-order-utils", "aioconsole", "aiohttp", - "aiokafka", + "asyncssh", "appdirs", "appnope", "async-timeout", "bidict", + "base58", "cachetools", "certifi", "cryptography", "cython", "cytoolz", + "commlib-py", "docker", "diff-cover", "dydx-python", @@ -71,6 +73,7 @@ def main(): "flake8", "hexbytes", "importlib-metadata", + "injective-py" "mypy-extensions", "nose", "nose-exclude", @@ -85,6 +88,7 @@ def main(): "pyperclip", "python-dateutil", "python-telegram-bot", + "pyOpenSSL", "requests", "rsa", "ruamel-yaml", diff --git a/setup/environment.yml b/setup/environment.yml index 4ad66f63123..1fe8cb0ee59 100644 --- a/setup/environment.yml +++ b/setup/environment.yml @@ -10,37 +10,36 @@ dependencies: - nomkl=1.0 - nose=1.3.7 - nose-exclude - - numpy=1.20.1 - - numpy-base=1.20.1 - - pandas=1.2.1 - - pip=21.1.2 + - numpy=1.23.5 + - numpy-base=1.23.5 + - pandas=1.5.3 + - pip=23.1.2 - prompt_toolkit=3.0.20 - - pydantic=1.9 - - pytables=3.6.1 - - python=3.8.2 - - scipy=1.6.2 - - sqlalchemy=1.4 + - pydantic=1.9.2 + - pytest==7.3.2 + - python=3.10.12 + - pytables=3.8.0 + - scipy=1.10.1 - tabulate==0.8.9 - typing-extensions<4.6.0 - - ujson=1.35 - - zlib=1.2.11 + - zlib=1.2.13 - pip: - 0x-contract-addresses==3.0.0 - 0x-contract-wrappers==2.0.0 - 0x-order-utils==4.0.0 - aiohttp==3.* - - aiokafka==0.7.1 - aioprocessing==2.0.0 - aioresponses - appdirs==1.4.3 - async-timeout - - asyncssh==2.7.2 + - asyncssh==2.13.1 + - pyOpenSSL==21.0.0 - appnope==0.1.3 - base58==2.1.1 - cachetools==4.0.0 - commlib-py==0.10.6 - cryptography==3.4.7 - - cython==3.0a7 + - cython==3.0.0a10 - diff-cover==5.1.2 - docker==5.0.3 - dydx-v3-python==2.0.1 @@ -56,15 +55,15 @@ dependencies: - ptpython==3.0.20 - pyjwt==1.7.1 - pyperclip==1.7.0 - - pytest==4.6.11 - - python-telegram-bot==12.4.2 + - python-telegram-bot==12.8 - requests==2.* - rsa==4.7 - ruamel-yaml==0.16.10 - signalr-client-aio==0.0.1.6.2 - - simplejson==3.17.2 + - sqlalchemy==1.4.* - substrate-interface==1.6.2 - solders==0.1.4 - - web3==5.* + - web3==5.31.4 - websockets==9.1 + - ujson==5.7.0 - git+https://github.com/CoinAlpha/python-signalr-client.git \ No newline at end of file diff --git a/test/hummingbot/client/command/test_config_command.py b/test/hummingbot/client/command/test_config_command.py index 6f344e40cab..712ff5c5d53 100644 --- a/test/hummingbot/client/command/test_config_command.py +++ b/test/hummingbot/client/command/test_config_command.py @@ -1,9 +1,8 @@ import asyncio import unittest -from collections import Awaitable from decimal import Decimal from test.mock.mock_cli import CLIMockingAssistant -from typing import Union +from typing import Awaitable, Union from unittest.mock import MagicMock, patch from pydantic import Field diff --git a/test/hummingbot/client/command/test_create_command.py b/test/hummingbot/client/command/test_create_command.py index 15ad53a5d02..0f3cecbd426 100644 --- a/test/hummingbot/client/command/test_create_command.py +++ b/test/hummingbot/client/command/test_create_command.py @@ -38,7 +38,7 @@ async def async_sleep(*_, **__): return async_sleep - def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 5): ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) return ret diff --git a/test/hummingbot/client/command/test_order_book_command.py b/test/hummingbot/client/command/test_order_book_command.py index 22e941fbe55..12f9e7db281 100644 --- a/test/hummingbot/client/command/test_order_book_command.py +++ b/test/hummingbot/client/command/test_order_book_command.py @@ -1,6 +1,6 @@ import asyncio import unittest -from collections import Awaitable +from typing import Awaitable from unittest.mock import MagicMock, patch from hummingbot.client.config.client_config_map import ClientConfigMap, DBSqliteMode diff --git a/test/hummingbot/client/command/test_ticker_command.py b/test/hummingbot/client/command/test_ticker_command.py index bbcb2f43171..6b9242251fb 100644 --- a/test/hummingbot/client/command/test_ticker_command.py +++ b/test/hummingbot/client/command/test_ticker_command.py @@ -1,6 +1,6 @@ import asyncio import unittest -from collections import Awaitable +from typing import Awaitable from unittest.mock import MagicMock, patch from hummingbot.client.config.client_config_map import ClientConfigMap, DBSqliteMode diff --git a/test/hummingbot/client/config/test_security.py b/test/hummingbot/client/config/test_security.py index 4712430b24b..0bd2a8779dc 100644 --- a/test/hummingbot/client/config/test_security.py +++ b/test/hummingbot/client/config/test_security.py @@ -14,9 +14,16 @@ ) from hummingbot.client.config.security import Security from hummingbot.connector.exchange.binance.binance_utils import BinanceConfigMap +from hummingbot.core.utils.async_call_scheduler import AsyncCallScheduler class SecurityTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + AsyncCallScheduler.shared_instance().reset_event_loop() + def setUp(self) -> None: super().setUp() self.ev_loop = asyncio.get_event_loop() @@ -44,7 +51,12 @@ def tearDown(self) -> None: self.reset_security() super().tearDown() - def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + @classmethod + def tearDownClass(cls) -> None: + super().tearDownClass() + AsyncCallScheduler.shared_instance().reset_event_loop() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 3): ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) return ret @@ -56,6 +68,10 @@ def store_binance_config(self) -> ClientConfigAdapter: save_to_yml(file_path, config_map) return config_map + @staticmethod + def reset_decryption_done(): + Security._decryption_done = asyncio.Event() + @staticmethod def reset_security(): Security.__instance = None @@ -83,8 +99,12 @@ def test_login(self): store_password_verification(secrets_manager) Security.login(secrets_manager) + self.async_run_with_timeout(Security.wait_til_decryption_done()) config_map = self.store_binance_config() - self.async_run_with_timeout(Security.wait_til_decryption_done(), timeout=2) + self.ev_loop.run_until_complete(asyncio.sleep(0.1)) + self.reset_decryption_done() + Security.decrypt_all() + self.async_run_with_timeout(Security.wait_til_decryption_done()) self.assertTrue(Security.is_decryption_done()) self.assertTrue(Security.any_secure_configs()) @@ -99,27 +119,34 @@ def test_update_secure_config(self): password = "som-password" secrets_manager = ETHKeyFileSecretManger(password) store_password_verification(secrets_manager) + Security.login(secrets_manager) + self.async_run_with_timeout(Security.wait_til_decryption_done()) + self.ev_loop.run_until_complete(asyncio.sleep(0.1)) + binance_config = ClientConfigAdapter( BinanceConfigMap(binance_api_key=self.api_key, binance_api_secret=self.api_secret) ) - self.async_run_with_timeout(Security.wait_til_decryption_done()) - Security.update_secure_config(binance_config) - self.reset_security() + self.ev_loop.run_until_complete(asyncio.sleep(0.1)) + + self.reset_decryption_done() + Security.decrypt_all() + self.ev_loop.run_until_complete(asyncio.sleep(0.1)) + self.async_run_with_timeout(Security.wait_til_decryption_done()) - Security.login(secrets_manager) - self.async_run_with_timeout(Security.wait_til_decryption_done(), timeout=2) binance_loaded_config = Security.decrypted_value(binance_config.connector) self.assertEqual(binance_config, binance_loaded_config) binance_config.binance_api_key = "someOtherApiKey" Security.update_secure_config(binance_config) - self.reset_security() - Security.login(secrets_manager) - self.async_run_with_timeout(Security.wait_til_decryption_done(), timeout=2) + self.reset_decryption_done() + Security.decrypt_all() + self.ev_loop.run_until_complete(asyncio.sleep(0.1)) + self.async_run_with_timeout(Security.wait_til_decryption_done()) + binance_loaded_config = Security.decrypted_value(binance_config.connector) self.assertEqual(binance_config, binance_loaded_config) diff --git a/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_order_book_data_source.py b/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_order_book_data_source.py index 7acb6a1ba6c..7b01ee9d540 100644 --- a/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_order_book_data_source.py +++ b/test/hummingbot/connector/derivative/bitget_perpetual/test_bitget_perpetual_order_book_data_source.py @@ -626,7 +626,7 @@ def test_listen_for_order_book_snapshots_log_exception(self, mock_api): self.data_source.listen_for_order_book_snapshots(self.ev_loop, msg_queue) ) try: - self.async_run_with_timeout(self.listening_task) + self.async_run_with_timeout(self.listening_task, timeout=2) except asyncio.CancelledError: pass diff --git a/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_auth.py b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_auth.py index b77b02c4424..d072716fa24 100644 --- a/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_auth.py +++ b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_auth.py @@ -5,10 +5,9 @@ import json import unittest from typing import Awaitable +from unittest.mock import patch from urllib.parse import urlencode -from mock import patch - from hummingbot.connector.derivative.bitmex_perpetual.bitmex_perpetual_auth import EXPIRATION, BitmexPerpetualAuth from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest diff --git a/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_derivative.py b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_derivative.py index d71f5ffadd8..3c747948e20 100644 --- a/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_derivative.py +++ b/test/hummingbot/connector/derivative/bitmex_perpetual/test_bitmex_perpetual_derivative.py @@ -122,7 +122,7 @@ def _create_exception_and_unlock_test_with_event(self, exception): self.resume_test_event.set() raise exception - def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 1): + def async_run_with_timeout(self, coroutine: Awaitable, timeout: float = 3): ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) return ret diff --git a/test/hummingbot/connector/exchange/bitmex/test_bitmex_auth.py b/test/hummingbot/connector/exchange/bitmex/test_bitmex_auth.py index b1062bfa591..ef4d625d2f0 100644 --- a/test/hummingbot/connector/exchange/bitmex/test_bitmex_auth.py +++ b/test/hummingbot/connector/exchange/bitmex/test_bitmex_auth.py @@ -5,10 +5,9 @@ import json import unittest from typing import Awaitable +from unittest.mock import patch from urllib.parse import urlencode -from mock import patch - from hummingbot.connector.exchange.bitmex.bitmex_auth import EXPIRATION, BitmexAuth from hummingbot.core.web_assistant.connections.data_types import RESTMethod, RESTRequest diff --git a/test/hummingbot/connector/exchange/bybit/test_bybit_exchange.py b/test/hummingbot/connector/exchange/bybit/test_bybit_exchange.py index 8b6fcee5209..0c4c91f4319 100644 --- a/test/hummingbot/connector/exchange/bybit/test_bybit_exchange.py +++ b/test/hummingbot/connector/exchange/bybit/test_bybit_exchange.py @@ -2,9 +2,8 @@ import json import re import unittest -from collections import Awaitable from decimal import Decimal -from typing import Dict, NamedTuple, Optional +from typing import Awaitable, Dict, NamedTuple, Optional from unittest.mock import AsyncMock, patch from aioresponses import aioresponses diff --git a/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_api_user_stream_data_source.py b/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_api_user_stream_data_source.py index 0458be1aea3..07312e1e9f9 100644 --- a/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_api_user_stream_data_source.py +++ b/test/hummingbot/connector/exchange/coinbase_pro/test_coinbase_pro_api_user_stream_data_source.py @@ -1,8 +1,7 @@ import asyncio import json import unittest -from collections import Awaitable -from typing import Dict, List +from typing import Awaitable, Dict, List from unittest.mock import AsyncMock, patch from hummingbot.connector.exchange.coinbase_pro.coinbase_pro_api_user_stream_data_source import ( diff --git a/test/hummingbot/connector/exchange/foxbit/test_foxbit_api_order_book_data_source.py b/test/hummingbot/connector/exchange/foxbit/test_foxbit_api_order_book_data_source.py index 30addcb3ec3..55d3b0adae9 100644 --- a/test/hummingbot/connector/exchange/foxbit/test_foxbit_api_order_book_data_source.py +++ b/test/hummingbot/connector/exchange/foxbit/test_foxbit_api_order_book_data_source.py @@ -155,6 +155,7 @@ def _level_1_response(self): } ] + @patch("hummingbot.connector.exchange.foxbit.foxbit_api_order_book_data_source.FoxbitAPIOrderBookDataSource._ORDER_BOOK_INTERVAL", 0.0) @aioresponses() def test_get_new_order_book_successful(self, mock_api): url = web_utils.public_rest_url(path_url=CONSTANTS.SNAPSHOT_PATH_URL.format(self.trading_pair), domain=self.domain) @@ -164,7 +165,7 @@ def test_get_new_order_book_successful(self, mock_api): order_book: OrderBook = self.async_run_with_timeout( coroutine=self.data_source.get_new_order_book(self.trading_pair), - timeout=2000 + timeout=2 ) expected_update_id = order_book.snapshot_uid @@ -189,7 +190,7 @@ def test_get_new_order_book_raises_exception(self, mock_api): with self.assertRaises(IOError): self.async_run_with_timeout( coroutine=self.data_source.get_new_order_book(self.trading_pair), - timeout=2000 + timeout=2 ) @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) diff --git a/test/hummingbot/connector/exchange/foxbit/test_foxbit_exchange.py b/test/hummingbot/connector/exchange/foxbit/test_foxbit_exchange.py index 864f1bd972e..8743e0f5cb9 100644 --- a/test/hummingbot/connector/exchange/foxbit/test_foxbit_exchange.py +++ b/test/hummingbot/connector/exchange/foxbit/test_foxbit_exchange.py @@ -906,7 +906,7 @@ def test_client_order_id_on_order(self): is_buy=True, ) - self.assertEqual(result[:12], expected_client_order_id[:12]) + self.assertEqual(result[:11], expected_client_order_id[:11]) self.assertEqual(result[:2], self.exchange.client_order_id_prefix) self.assertLess(len(expected_client_order_id), self.exchange.client_order_id_max_length) @@ -924,12 +924,17 @@ def test_client_order_id_on_order(self): def test_create_order(self): self._simulate_trading_rules_initialized() - _order = self.async_run_with_timeout(self.exchange._create_order(TradeType.BUY, - '551100', - self.trading_pair, - Decimal(1.01), - OrderType.LIMIT, - Decimal(22354.01))) + _order = self.async_run_with_timeout( + self.exchange._create_order( + TradeType.BUY, + '551100', + self.trading_pair, + Decimal(1.01), + OrderType.LIMIT, + Decimal(22354.01) + ), + 3 + ) self.assertIsNone(_order) @aioresponses() diff --git a/test/hummingbot/connector/exchange/foxbit/test_foxbit_user_stream_data_source.py b/test/hummingbot/connector/exchange/foxbit/test_foxbit_user_stream_data_source.py index b011d3e0376..9e0252175ab 100644 --- a/test/hummingbot/connector/exchange/foxbit/test_foxbit_user_stream_data_source.py +++ b/test/hummingbot/connector/exchange/foxbit/test_foxbit_user_stream_data_source.py @@ -2,7 +2,7 @@ import json import unittest from typing import Any, Awaitable, Dict, Optional -from unittest.mock import MagicMock +from unittest.mock import AsyncMock, MagicMock, patch from bidict import bidict @@ -127,11 +127,50 @@ def _successfully_subscribed_event(self): } return resp + def _fake_ws_auth_resp(self): + return { + 'APIKey': 'testApiKey', + 'UserId': 'testUserId', + 'Nonce': 1687956360722, + 'Content-Type': 'application/json', + 'User-Agent': 'HBOT', + 'n': 'AuthenticateUser', + 'o': '{"Authenticated":true,"Requires2FA":false,"EnforceEnable2FA":false,"TwoFAToken":null}' + } + + def _fake_ws_sub_resp(self): + return { + 'APIKey': 'testApiKey', + 'Signature': '00000', + 'UserId': 'testUserId', + 'Nonce': 1687956360722, + 'Content-Type': 'application/json', + 'User-Agent': 'HBOT', + 'o': '{"Subscribed":true}' + } + def test_user_stream_properties(self): self.assertEqual(self.data_source.ready, self.data_source._user_stream_data_source_initialized) - async def test_run_ws_assistant(self): - ws: WSAssistant = await self.data_source._connected_websocket_assistant() + @patch( + "hummingbot.connector.exchange.foxbit.foxbit_api_user_stream_data_source.FoxbitAPIUserStreamDataSource._INTERVAL_SLEEP_INTERRUPTION", + 0.0 + ) + @patch("aiohttp.ClientSession.ws_connect", new_callable=AsyncMock) + def test_run_ws_assistant(self, ws_connect_mock): + ws_connect_mock.return_value = self.mocking_assistant.create_websocket_mock() + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self._fake_ws_auth_resp())) + + ws: WSAssistant = self.async_run_with_timeout(self.data_source._connected_websocket_assistant(), 3) self.assertIsNotNone(ws) - await self.data_source._subscribe_channels(ws) - await self.data_source._on_user_stream_interruption(ws) + + self.mocking_assistant.add_websocket_aiohttp_message( + websocket_mock=ws_connect_mock.return_value, + message=json.dumps(self._fake_ws_sub_resp())) + + self.async_run_with_timeout(self.data_source._subscribe_channels(ws), 3) + + self.async_run_with_timeout(self.data_source._on_user_stream_interruption(ws), 3) diff --git a/test/hummingbot/connector/exchange/kraken/test_kraken_api_order_book_data_source.py b/test/hummingbot/connector/exchange/kraken/test_kraken_api_order_book_data_source.py index 6e0e01285a9..a3ed3f811f0 100644 --- a/test/hummingbot/connector/exchange/kraken/test_kraken_api_order_book_data_source.py +++ b/test/hummingbot/connector/exchange/kraken/test_kraken_api_order_book_data_source.py @@ -33,7 +33,7 @@ def setUp(self) -> None: self.throttler = AsyncThrottler(build_rate_limits_by_tier(self.api_tier)) self.data_source = KrakenAPIOrderBookDataSource(self.throttler, trading_pairs=[self.trading_pair]) - def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 3): ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) return ret @@ -239,7 +239,8 @@ def test_fetch_trading_pairs(self, mocked_api): url = f"{CONSTANTS.BASE_URL}{CONSTANTS.ASSET_PAIRS_PATH_URL}" regex_url = re.compile(f"^{url}".replace(".", r"\.").replace("?", r"\?")) resp = self.get_public_asset_pair_mock() - mocked_api.get(regex_url, body=json.dumps(resp)) + for _hbot_start_fetch_fix in range(2): + mocked_api.get(regex_url, body=json.dumps(resp)) resp = self.async_run_with_timeout(KrakenAPIOrderBookDataSource.fetch_trading_pairs()) diff --git a/test/hummingbot/connector/exchange/kraken/test_kraken_in_flight_order.py b/test/hummingbot/connector/exchange/kraken/test_kraken_in_flight_order.py index 6de2297f5f1..95ccfd467da 100644 --- a/test/hummingbot/connector/exchange/kraken/test_kraken_in_flight_order.py +++ b/test/hummingbot/connector/exchange/kraken/test_kraken_in_flight_order.py @@ -9,7 +9,7 @@ class KrakenInFlightOrderTests(TestCase): def test_order_is_local_after_creation(self): order = KrakenInFlightOrder( client_order_id="someId", - exchange_order_id=None, + exchange_order_id="", trading_pair="BTC-USDT", order_type=OrderType.LIMIT, trade_type=TradeType.BUY, diff --git a/test/hummingbot/connector/exchange/lbank/test_lbank_utils.py b/test/hummingbot/connector/exchange/lbank/test_lbank_utils.py index 793f673da5c..c3d11435717 100644 --- a/test/hummingbot/connector/exchange/lbank/test_lbank_utils.py +++ b/test/hummingbot/connector/exchange/lbank/test_lbank_utils.py @@ -1,7 +1,7 @@ import json import unittest +from unittest.mock import MagicMock, patch -from mock.mock import MagicMock, patch from pydantic import SecretStr, ValidationError, validate_model from hummingbot.connector.exchange.lbank.lbank_auth import LbankAuth diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_api_order_book_data_source.py b/test/hummingbot/connector/exchange/mexc/test_mexc_api_order_book_data_source.py index 11fc671c5b9..ecaab81654f 100644 --- a/test/hummingbot/connector/exchange/mexc/test_mexc_api_order_book_data_source.py +++ b/test/hummingbot/connector/exchange/mexc/test_mexc_api_order_book_data_source.py @@ -2,8 +2,8 @@ import json import re import unittest -from collections import Awaitable, deque -from typing import Any, Dict +from collections import deque +from typing import Any, Awaitable, Dict from unittest.mock import AsyncMock, patch import ujson diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_api_user_stream_data_source.py b/test/hummingbot/connector/exchange/mexc/test_mexc_api_user_stream_data_source.py index 942a084c576..a3173e49278 100644 --- a/test/hummingbot/connector/exchange/mexc/test_mexc_api_user_stream_data_source.py +++ b/test/hummingbot/connector/exchange/mexc/test_mexc_api_user_stream_data_source.py @@ -1,5 +1,5 @@ import asyncio -from collections import Awaitable +from typing import Awaitable from unittest import TestCase from unittest.mock import AsyncMock, patch diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_exchange.py b/test/hummingbot/connector/exchange/mexc/test_mexc_exchange.py index e5edb56e046..98b55728db7 100644 --- a/test/hummingbot/connector/exchange/mexc/test_mexc_exchange.py +++ b/test/hummingbot/connector/exchange/mexc/test_mexc_exchange.py @@ -3,9 +3,8 @@ import json import re import time -from collections import Awaitable from decimal import Decimal -from typing import Any, Callable, Dict, List +from typing import Any, Awaitable, Callable, Dict, List from unittest import TestCase from unittest.mock import AsyncMock, PropertyMock, patch diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_order_book_tracker.py b/test/hummingbot/connector/exchange/mexc/test_mexc_order_book_tracker.py index 26eac2bb6fe..41c4da5a180 100644 --- a/test/hummingbot/connector/exchange/mexc/test_mexc_order_book_tracker.py +++ b/test/hummingbot/connector/exchange/mexc/test_mexc_order_book_tracker.py @@ -2,9 +2,8 @@ import asyncio import json import unittest -from collections import Awaitable from decimal import Decimal -from typing import Any +from typing import Any, Awaitable from unittest.mock import AsyncMock from aioresponses import aioresponses diff --git a/test/hummingbot/connector/exchange/mexc/test_mexc_user_stream_tracker.py b/test/hummingbot/connector/exchange/mexc/test_mexc_user_stream_tracker.py index 1257ccd589a..bfcdae88e35 100644 --- a/test/hummingbot/connector/exchange/mexc/test_mexc_user_stream_tracker.py +++ b/test/hummingbot/connector/exchange/mexc/test_mexc_user_stream_tracker.py @@ -1,5 +1,5 @@ import asyncio -from collections import Awaitable +from typing import Awaitable from unittest import TestCase from unittest.mock import AsyncMock, patch diff --git a/test/hummingbot/connector/exchange/probit/test_probit_api_user_stream_data_source.py b/test/hummingbot/connector/exchange/probit/test_probit_api_user_stream_data_source.py index f6fb7c04a4b..15ea515d8ba 100644 --- a/test/hummingbot/connector/exchange/probit/test_probit_api_user_stream_data_source.py +++ b/test/hummingbot/connector/exchange/probit/test_probit_api_user_stream_data_source.py @@ -1,8 +1,7 @@ import asyncio import json import unittest -from collections import Awaitable -from typing import Optional +from typing import Awaitable, Optional from unittest.mock import AsyncMock, patch from aiohttp import WSMsgType diff --git a/test/hummingbot/data_feed/candles_feed/kucoin_spot_candles/test_kucoin_spot_candles.py b/test/hummingbot/data_feed/candles_feed/kucoin_spot_candles/test_kucoin_spot_candles.py index c7526b667c7..9a52bb1598b 100644 --- a/test/hummingbot/data_feed/candles_feed/kucoin_spot_candles/test_kucoin_spot_candles.py +++ b/test/hummingbot/data_feed/candles_feed/kucoin_spot_candles/test_kucoin_spot_candles.py @@ -43,7 +43,7 @@ def is_logged(self, log_level: str, message: str) -> bool: record.levelname == log_level and record.getMessage() == message for record in self.log_records) - def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 3): ret = asyncio.get_event_loop().run_until_complete(asyncio.wait_for(coroutine, timeout)) return ret diff --git a/test/hummingbot/remote_iface/test_mqtt.py b/test/hummingbot/remote_iface/test_mqtt.py index 328f5b6824a..fce062289fe 100644 --- a/test/hummingbot/remote_iface/test_mqtt.py +++ b/test/hummingbot/remote_iface/test_mqtt.py @@ -15,6 +15,7 @@ from hummingbot.core.data_type.limit_order import LimitOrder from hummingbot.core.event.events import BuyOrderCreatedEvent, MarketEvent, OrderExpiredEvent, SellOrderCreatedEvent from hummingbot.core.mock_api.mock_mqtt_server import FakeMQTTBroker +from hummingbot.core.utils.async_call_scheduler import AsyncCallScheduler from hummingbot.model.order import Order from hummingbot.model.trade_fill import TradeFill from hummingbot.remote_iface.mqtt import MQTTGateway, MQTTMarketEventForwarder @@ -29,6 +30,7 @@ class RemoteIfaceMQTTTests(TestCase): @classmethod def setUpClass(cls): super().setUpClass() + AsyncCallScheduler.shared_instance().reset_event_loop() cls.instance_id = 'TEST_ID' cls.fake_err_msg = "Some error" cls.client_config_map = ClientConfigAdapter(ClientConfigMap()) @@ -67,6 +69,7 @@ def tearDownClass(cls) -> None: cls.client_config_map.instance_id = cls.prev_instance_id del cls.fake_mqtt_broker super().tearDownClass() + AsyncCallScheduler.shared_instance().reset_event_loop() def setUp(self) -> None: super().setUp() @@ -107,6 +110,7 @@ def setUp(self) -> None: self.patch_loggers_mock.return_value = None def tearDown(self): + self.ev_loop.run_until_complete(asyncio.sleep(0.1)) self.gateway.stop() del self.gateway self.ev_loop.run_until_complete(asyncio.sleep(0.1)) @@ -340,6 +344,7 @@ def test_mqtt_command_balance_limit_failure( topic = f"test_reply/hbot/{self.instance_id}/balance/limit" msg = {'status': 400, 'msg': self.fake_err_msg, 'data': ''} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) def test_mqtt_command_balance_paper(self): @@ -378,6 +383,7 @@ def test_mqtt_command_balance_paper_failure( topic = f"test_reply/hbot/{self.instance_id}/balance/paper" msg = {'status': 400, 'msg': self.fake_err_msg, 'data': ''} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) def test_mqtt_command_command_shortcuts(self): @@ -397,6 +403,7 @@ def test_mqtt_command_command_shortcuts(self): reply_data = {'success': [True], 'status': 200, 'msg': ''} self.ev_loop.run_until_complete(self.wait_for_rcv(reply_topic, reply_data, msg_key='data')) for notify_msg in notify_msgs: + self.ev_loop.run_until_complete(self.wait_for_rcv(notify_topic, notify_msg)) self.assertTrue(self.is_msg_received(notify_topic, notify_msg)) @patch("hummingbot.client.hummingbot_application.HummingbotApplication._handle_shortcut") @@ -416,6 +423,7 @@ def test_mqtt_command_command_shortcuts_failure( topic = f"test_reply/hbot/{self.instance_id}/command_shortcuts" msg = {'success': [], 'status': 400, 'msg': self.fake_err_msg} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) def test_mqtt_command_config(self): @@ -496,6 +504,7 @@ def test_mqtt_command_config_updates_configurable_keys( ) topic = f"test_reply/hbot/{self.instance_id}/config" msg = {'changes': [], 'config': {}, 'status': 400, 'msg': "Invalid param key(s): ['skata']"} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) def test_mqtt_command_config_updates_multiple_params(self): @@ -527,6 +536,7 @@ def test_mqtt_command_config_failure( topic = f"test_reply/hbot/{self.instance_id}/config" msg = {'changes': [], 'config': {}, 'status': 400, 'msg': self.fake_err_msg} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) @patch("hummingbot.client.command.history_command.HistoryCommand.get_history_trades_json") @@ -576,6 +586,7 @@ def test_mqtt_command_history_failure( topic = f"test_reply/hbot/{self.instance_id}/history" msg = {'status': 400, 'msg': self.fake_err_msg, 'trades': []} + self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) @patch("hummingbot.client.command.import_command.load_strategy_config_map_from_file") @@ -814,6 +825,7 @@ def test_mqtt_command_status_async( self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) self.hbapp.strategy = None + self.ev_loop.run_until_complete(asyncio.sleep(0.2)) @patch("hummingbot.client.command.status_command.StatusCommand.strategy_status", new_callable=AsyncMock) def test_mqtt_command_status_sync( @@ -845,6 +857,7 @@ def test_mqtt_command_status_failure( msg = {'status': 400, 'msg': 'No strategy is currently running!', 'data': ''} self.ev_loop.run_until_complete(self.wait_for_rcv(topic, msg, msg_key='data')) self.assertTrue(self.is_msg_received(topic, msg, msg_key='data')) + self.ev_loop.run_until_complete(asyncio.sleep(0.2)) def test_mqtt_command_stop_sync(self): self.start_mqtt() diff --git a/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making.py b/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making.py index 97f7730d05b..5e536fbfc19 100644 --- a/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making.py +++ b/test/hummingbot/strategy/cross_exchange_market_making/test_cross_exchange_market_making.py @@ -155,7 +155,10 @@ def setUp(self, get_connector_settings_mock, get_exchange_names_mock): self.taker_market.add_listener(MarketEvent.BuyOrderCreated, self.taker_order_created_logger) self.taker_market.add_listener(MarketEvent.SellOrderCreated, self.taker_order_created_logger) - def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 1): + def tearDown(self): + super().tearDown() + + def async_run_with_timeout(self, coroutine: Awaitable, timeout: int = 2): ret = self.ev_loop.run_until_complete(asyncio.wait_for(coroutine, timeout)) return ret diff --git a/test/hummingbot/strategy/perpetual_market_making/test_perpetual_market_making_config_map.py b/test/hummingbot/strategy/perpetual_market_making/test_perpetual_market_making_config_map.py index 320e9c917e0..8b19b4f921d 100644 --- a/test/hummingbot/strategy/perpetual_market_making/test_perpetual_market_making_config_map.py +++ b/test/hummingbot/strategy/perpetual_market_making/test_perpetual_market_making_config_map.py @@ -1,7 +1,6 @@ -import mock import unittest from copy import deepcopy -from unittest.mock import MagicMock +from unittest.mock import MagicMock, PropertyMock, patch import hummingbot.strategy.perpetual_market_making.perpetual_market_making_config_map as config_map_module from hummingbot.client.settings import AllConnectorSettings @@ -109,8 +108,8 @@ def test_maker_trading_prompt(self): def test_validate_derivative_trading_pair(self): fetcher_mock = MagicMock() - type(fetcher_mock).ready = mock.PropertyMock(return_value=True) - type(fetcher_mock).trading_pairs = mock.PropertyMock(return_value={"test_market": ["BTC-USDT"]}) + type(fetcher_mock).ready = PropertyMock(return_value=True) + type(fetcher_mock).trading_pairs = PropertyMock(return_value={"test_market": ["BTC-USDT"]}) TradingPairFetcher._sf_shared_instance = fetcher_mock perpetual_mm_config_map.get("derivative").value = "test_market" @@ -146,8 +145,8 @@ def test_price_source_market_prompt(self): config_map_module.price_source_market_prompt() ) - @mock.patch("hummingbot.client.settings.AllConnectorSettings.get_derivative_names") - @mock.patch("hummingbot.client.settings.AllConnectorSettings.get_exchange_names") + @patch("hummingbot.client.settings.AllConnectorSettings.get_derivative_names") + @patch("hummingbot.client.settings.AllConnectorSettings.get_exchange_names") def test_price_source_derivative_validator(self, get_derivatives_mock, get_exchange_mock): get_derivatives_mock.return_value = ["derivative_connector"] get_exchange_mock.return_value = ["exchange_connector"] @@ -175,8 +174,8 @@ def test_on_validate_price_source_derivative(self): def test_validate_price_source_market(self): fetcher_mock = MagicMock() - type(fetcher_mock).ready = mock.PropertyMock(return_value=True) - type(fetcher_mock).trading_pairs = mock.PropertyMock(return_value={"test_market": ["BTC-USDT"]}) + type(fetcher_mock).ready = PropertyMock(return_value=True) + type(fetcher_mock).trading_pairs = PropertyMock(return_value={"test_market": ["BTC-USDT"]}) TradingPairFetcher._sf_shared_instance = fetcher_mock perpetual_mm_config_map.get("price_source_derivative").value = "test_market" diff --git a/test/hummingbot/strategy/test_hanging_orders_tracker.py b/test/hummingbot/strategy/test_hanging_orders_tracker.py index 59bf0efe3a8..92576ae8c19 100644 --- a/test/hummingbot/strategy/test_hanging_orders_tracker.py +++ b/test/hummingbot/strategy/test_hanging_orders_tracker.py @@ -1,19 +1,12 @@ import unittest -from decimal import Decimal from datetime import datetime -from mock import MagicMock, PropertyMock +from decimal import Decimal +from unittest.mock import MagicMock, PropertyMock from hummingbot.core.data_type.limit_order import LimitOrder -from hummingbot.core.event.events import ( - BuyOrderCompletedEvent, - MarketEvent, - OrderCancelledEvent, -) +from hummingbot.core.event.events import BuyOrderCompletedEvent, MarketEvent, OrderCancelledEvent from hummingbot.strategy.data_types import OrderType -from hummingbot.strategy.hanging_orders_tracker import ( - CreatedPairOfOrders, - HangingOrdersTracker, -) +from hummingbot.strategy.hanging_orders_tracker import CreatedPairOfOrders, HangingOrdersTracker class TestHangingOrdersTracker(unittest.TestCase):