Custom HummingBot for Whitebit
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
hummingbot/test/debug/test_config_process.py

187 lines
9.0 KiB

#!/usr/bin/env python
import asyncio
import inspect
import os
import time
import unittest
from os.path import join, realpath
from test.debug.fixture_configs import FixtureConfigs
from bin.hummingbot import main as hb_main
from hummingbot.client import settings
from hummingbot.client.config.global_config_map import global_config_map
from hummingbot.client.config.security import Security
from hummingbot.client.hummingbot_application import HummingbotApplication
from hummingbot.strategy.pure_market_making.pure_market_making_config_map import pure_market_making_config_map
import sys; sys.path.insert(0, realpath(join(__file__, "../../")))
import sys; sys.path.append(realpath(join(__file__, "../../bin")))
async def wait_til(condition_func, timeout=10):
start_time = time.perf_counter()
while True:
if condition_func():
return
elif time.perf_counter() - start_time > timeout:
raise Exception(f"{inspect.getsource(condition_func).strip()} condition is never met. Time out reached.")
else:
await asyncio.sleep(0.1)
async def wait_til_notified(text):
await wait_til(lambda: text in HummingbotApplication.main_application().app.output_field.document.lines[:-1])
def user_response(text):
hb = HummingbotApplication.main_application()
hb.app.set_text(text)
hb.app.accept(None)
hb.app.set_text("")
def add_files_extension(folder, file_extensions, additional_extension):
for f in os.listdir(folder):
f_path = os.path.join(folder, f)
if os.path.isfile(f_path):
extension = os.path.splitext(f_path)[1]
if extension in file_extensions:
os.rename(f_path, f_path + f"{additional_extension}")
def remove_files_extension(folder, file_extension):
for f in os.listdir(folder):
f_path = os.path.join(folder, f)
if os.path.isfile(f_path):
extension = os.path.splitext(f_path)[1]
if extension == file_extension:
os.rename(f_path, os.path.splitext(f_path)[0])
def remove_files(folder, file_extensions):
for f in os.listdir(folder):
f_path = os.path.join(folder, f)
if os.path.isfile(f_path):
extension = os.path.splitext(f_path)[1]
if extension in file_extensions:
os.remove(f_path)
class ConfigProcessTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.ev_loop = asyncio.new_event_loop()
cls.ev_loop.run_until_complete(cls.set_up_class())
cls.file_no = 0
@classmethod
def tearDownClass(cls) -> None:
remove_files(settings.STRATEGIES_CONF_DIR_PATH, [".yml", ".json"])
remove_files_extension(settings.STRATEGIES_CONF_DIR_PATH, ".temp")
user_response("stop")
cls.ev_loop.run_until_complete(wait_til(lambda: cls.hb.markets_recorder is None))
@classmethod
async def set_up_class(cls):
add_files_extension(settings.STRATEGIES_CONF_DIR_PATH, [".yml", ".json"], ".temp")
asyncio.ensure_future(hb_main())
cls.hb = HummingbotApplication.main_application()
await wait_til(lambda: 'Enter "config" to create a bot' in cls.hb.app.output_field.document.text)
async def check_prompt_and_input(self, expected_prompt_text, input_text):
self.assertEqual(self.hb.app.prompt_text, expected_prompt_text)
last_output = str(self.hb.app.output_field.document.lines[-1])
user_response(input_text)
await wait_til(lambda: str(self.hb.app.output_field.document.lines[-1]) != last_output)
await asyncio.sleep(0.1)
async def _test_pure_mm_basic_til_start(self):
config_file_name = f"{settings.CONF_PREFIX}pure_market_making{settings.CONF_POSTFIX}_{self.file_no}.yml"
await self.check_prompt_and_input(">>> ", "config")
# For the second time this test is called, it's to reconfigure the bot
if self.file_no > 0:
await self.check_prompt_and_input("Would you like to reconfigure the bot? (Yes/No) >>> ", "yes")
ConfigProcessTest.file_no += 1
fixture_in_mem = FixtureConfigs.in_mem_new_pass_configs if Security.password is None \
else FixtureConfigs.in_mem_existing_pass_create_configs
for fixture_config in fixture_in_mem:
await self.check_prompt_and_input(fixture_config["prompt"], fixture_config["input"])
await wait_til(lambda: f'A new config file {config_file_name}' in self.hb.app.output_field.document.text)
# configs that are required will be prompted
for config_name, response in FixtureConfigs.pure_mm_basic_responses.items():
config = pure_market_making_config_map[config_name]
await self.check_prompt_and_input(config.prompt, response)
# advance_mode will be asked again as the previous response is not valid.
await asyncio.sleep(0.2)
self.assertEqual(self.hb.app.output_field.document.lines[-1],
f"{FixtureConfigs.pure_mm_basic_responses['advanced_mode']} "
f"is not a valid advanced_mode value")
await self.check_prompt_and_input(pure_market_making_config_map["advanced_mode"].prompt, "no")
# input for cancel_order_wait_time is empty, check the assigned value is its default value
self.assertEqual(pure_market_making_config_map["cancel_order_wait_time"].value,
pure_market_making_config_map["cancel_order_wait_time"].default)
# Check that configs that are not prompted get assigned correct default value
for name, config in pure_market_making_config_map.items():
if config.default is not None and name not in FixtureConfigs.pure_mm_basic_responses:
self.assertEqual(config.value, config.default)
# if not conf_global_file_exists:
for name, config in global_config_map.items():
if config.required and config.value is None:
await self.check_prompt_and_input(config.prompt, FixtureConfigs.global_binance_config[name])
self.assertEqual(pure_market_making_config_map["mode"].value,
pure_market_making_config_map["mode"].default)
await wait_til(lambda: "Config process complete." in self.hb.app.output_field.document.text)
def test_pure_mm_basic_til_start(self):
self.ev_loop.run_until_complete(self._test_pure_mm_basic_til_start())
async def _test_pure_mm_basic_import_config_file(self):
config_file_name = f"{settings.CONF_PREFIX}pure_market_making{settings.CONF_POSTFIX}_0.yml"
# update the config file to put in some blank and invalid values.
with open(os.path.join(settings.STRATEGIES_CONF_DIR_PATH, config_file_name), "r+") as f:
content = f.read() # read everything in the file
f.seek(0) # rewind
content = content.replace("bid_place_threshold: 0.01", "bid_place_threshold: ")
content = content.replace("advanced_mode: false", "advanced_mode: better not")
f.write(content) # write the new line before
await self.check_prompt_and_input(">>> ", "stop")
await self.check_prompt_and_input(">>> ", "config")
await self.check_prompt_and_input("Would you like to reconfigure the bot? (Yes/No) >>> ", "yes")
for fixture_config in FixtureConfigs.in_mem_existing_pass_import_configs:
await self.check_prompt_and_input(fixture_config["prompt"], fixture_config["input"])
# await self.check_prompt_and_input(default_strategy_conf_path_prompt(), config_file_name)
# advanced_mode should be prompted here as its file value not valid.
await self.check_prompt_and_input(pure_market_making_config_map["bid_place_threshold"].prompt, "0.01")
await self.check_prompt_and_input(pure_market_making_config_map["advanced_mode"].prompt, "no")
await wait_til(lambda: "Config process complete." in self.hb.app.output_field.document.text)
def test_pure_mm_basic_import_config_file(self):
self.ev_loop.run_until_complete(self._test_pure_mm_basic_import_config_file())
async def _test_single_configs(self):
await self.check_prompt_and_input(">>> ", "config bid_place_threshold")
# try inputting invalid value
await self.check_prompt_and_input(pure_market_making_config_map["bid_place_threshold"].prompt, "-0.01")
self.assertEqual(self.hb.app.output_field.document.lines[-1], "-0.01 is not a valid bid_place_threshold value")
await self.check_prompt_and_input(pure_market_making_config_map["bid_place_threshold"].prompt, "0.01")
def test_single_configs(self):
self.ev_loop.run_until_complete(self._test_single_configs())
def suite():
suite = unittest.TestSuite()
suite.addTest(ConfigProcessTest('test_pure_mm_basic_til_start'))
suite.addTest(ConfigProcessTest('test_single_configs'))
suite.addTest(ConfigProcessTest('test_pure_mm_basic_import_config_file'))
suite.addTest(ConfigProcessTest('test_pure_mm_basic_til_start'))
return suite
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())