Refactor Configuration() to apply common configurations all the time and to remove show_info
This commit is contained in:
parent
0632cf0f44
commit
84759073d9
@ -17,14 +17,13 @@ class Configuration(object):
|
|||||||
Class to read and init the bot configuration
|
Class to read and init the bot configuration
|
||||||
Reuse this class for the bot, backtesting, hyperopt and every script that required configuration
|
Reuse this class for the bot, backtesting, hyperopt and every script that required configuration
|
||||||
"""
|
"""
|
||||||
def __init__(self, args: List[str]) -> None:
|
def __init__(self, args: List[str], do_not_init=False) -> None:
|
||||||
self.args = args
|
self.args = args
|
||||||
self.logging = Logger(name=__name__)
|
self.logging = Logger(name=__name__)
|
||||||
self.logger = self.logging.get_logger()
|
self.logger = self.logging.get_logger()
|
||||||
self.config = self._load_config()
|
self.config = None
|
||||||
self.show_info()
|
|
||||||
|
|
||||||
def _load_config(self) -> Dict[str, Any]:
|
def load_config(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Extract information for sys.argv and load the bot configuration
|
Extract information for sys.argv and load the bot configuration
|
||||||
:return: Configuration dictionary
|
:return: Configuration dictionary
|
||||||
@ -35,18 +34,8 @@ class Configuration(object):
|
|||||||
# Add the strategy file to use
|
# Add the strategy file to use
|
||||||
config.update({'strategy': self.args.strategy})
|
config.update({'strategy': self.args.strategy})
|
||||||
|
|
||||||
# Add dynamic_whitelist if found
|
# Load Common configuration
|
||||||
if 'dynamic_whitelist' in self.args and self.args.dynamic_whitelist:
|
config = self._load_common_config(config)
|
||||||
config.update({'dynamic_whitelist': self.args.dynamic_whitelist})
|
|
||||||
|
|
||||||
# Add dry_run_db if found and the bot in dry run
|
|
||||||
if self.args.dry_run_db and config.get('dry_run', False):
|
|
||||||
config.update({'dry_run_db': True})
|
|
||||||
|
|
||||||
# Log level
|
|
||||||
if 'loglevel' in self.args and self.args.loglevel:
|
|
||||||
config.update({'loglevel': self.args.loglevel})
|
|
||||||
self.logging.set_level(self.args.loglevel)
|
|
||||||
|
|
||||||
# Load Backtesting
|
# Load Backtesting
|
||||||
config = self._load_backtesting_config(config)
|
config = self._load_backtesting_config(config)
|
||||||
@ -71,11 +60,46 @@ class Configuration(object):
|
|||||||
|
|
||||||
return self._validate_config(conf)
|
return self._validate_config(conf)
|
||||||
|
|
||||||
|
def _load_common_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Extract information for sys.argv and load common configuration
|
||||||
|
:return: configuration as dictionary
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Log level
|
||||||
|
if 'loglevel' in self.args and self.args.loglevel:
|
||||||
|
config.update({'loglevel': self.args.loglevel})
|
||||||
|
self.logging.set_level(self.args.loglevel)
|
||||||
|
self.logger.info('Log level set at %s', config['loglevel'])
|
||||||
|
|
||||||
|
# Add dynamic_whitelist if found
|
||||||
|
if 'dynamic_whitelist' in self.args and self.args.dynamic_whitelist:
|
||||||
|
config.update({'dynamic_whitelist': self.args.dynamic_whitelist})
|
||||||
|
self.logger.info(
|
||||||
|
'Parameter --dynamic-whitelist detected. '
|
||||||
|
'Using dynamically generated whitelist. '
|
||||||
|
'(not applicable with Backtesting and Hyperopt)'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add dry_run_db if found and the bot in dry run
|
||||||
|
if self.args.dry_run_db and config.get('dry_run', False):
|
||||||
|
config.update({'dry_run_db': True})
|
||||||
|
self.logger.info('Parameter --dry-run-db detected ...')
|
||||||
|
|
||||||
|
if config.get('dry_run_db', False):
|
||||||
|
if config.get('dry_run', False):
|
||||||
|
self.logger.info('Dry_run will use the DB file: "tradesv3.dry_run.sqlite"')
|
||||||
|
else:
|
||||||
|
self.logger.info('Dry run is disabled. (--dry_run_db ignored)')
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Extract information for sys.argv and load Backtesting configuration
|
Extract information for sys.argv and load Backtesting configuration
|
||||||
:return: configuration as dictionary
|
:return: configuration as dictionary
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# If -i/--ticker-interval is used we override the configuration parameter
|
# If -i/--ticker-interval is used we override the configuration parameter
|
||||||
# (that will override the strategy configuration)
|
# (that will override the strategy configuration)
|
||||||
if 'ticker_interval' in self.args and self.args.ticker_interval:
|
if 'ticker_interval' in self.args and self.args.ticker_interval:
|
||||||
@ -152,28 +176,12 @@ class Configuration(object):
|
|||||||
best_match(Draft4Validator(Constants.CONF_SCHEMA).iter_errors(conf)).message
|
best_match(Draft4Validator(Constants.CONF_SCHEMA).iter_errors(conf)).message
|
||||||
)
|
)
|
||||||
|
|
||||||
def show_info(self) -> None:
|
|
||||||
"""
|
|
||||||
Display info message to user depending of the configuration of the bot
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
if self.config.get('dynamic_whitelist', False):
|
|
||||||
self.logger.info(
|
|
||||||
'Using dynamically generated whitelist. (--dynamic-whitelist detected)'
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.config.get('dry_run_db', False):
|
|
||||||
if self.config.get('dry_run', False):
|
|
||||||
self.logger.info(
|
|
||||||
'Dry_run will use the DB file: "tradesv3.dry_run.sqlite". '
|
|
||||||
'(--dry_run_db detected)'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.logger.info('Dry run is disabled. (--dry_run_db ignored)')
|
|
||||||
|
|
||||||
def get_config(self) -> Dict[str, Any]:
|
def get_config(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Return the config. Use this method to get the bot config
|
Return the config. Use this method to get the bot config
|
||||||
:return: Dict: Bot config
|
:return: Dict: Bot config
|
||||||
"""
|
"""
|
||||||
|
if self.config is None:
|
||||||
|
self.config = self.load_config()
|
||||||
|
|
||||||
return self.config
|
return self.config
|
||||||
|
@ -69,4 +69,3 @@ def file_dump_json(filename, data) -> None:
|
|||||||
"""
|
"""
|
||||||
with open(filename, 'w') as file:
|
with open(filename, 'w') as file:
|
||||||
json.dump(data, file)
|
json.dump(data, file)
|
||||||
|
|
||||||
|
@ -293,6 +293,7 @@ def setup_configuration(args) -> Dict[str, Any]:
|
|||||||
:return: Configuration
|
:return: Configuration
|
||||||
"""
|
"""
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
|
|
||||||
config = configuration.get_config()
|
config = configuration.get_config()
|
||||||
|
|
||||||
# Ensure we do not use Exchange credentials
|
# Ensure we do not use Exchange credentials
|
||||||
|
@ -589,7 +589,8 @@ def start(args) -> None:
|
|||||||
# Monkey patch the configuration with hyperopt_conf.py
|
# Monkey patch the configuration with hyperopt_conf.py
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
optimize_config = hyperopt_optimize_conf()
|
optimize_config = hyperopt_optimize_conf()
|
||||||
config = configuration._load_backtesting_config(optimize_config)
|
config = configuration._load_common_config(optimize_config)
|
||||||
|
config = configuration._load_backtesting_config(config)
|
||||||
config = configuration._load_hyperopt_config(config)
|
config = configuration._load_hyperopt_config(config)
|
||||||
config['exchange']['key'] = ''
|
config['exchange']['key'] = ''
|
||||||
config['exchange']['secret'] = ''
|
config['exchange']['secret'] = ''
|
||||||
|
@ -6,7 +6,6 @@ Unit test file for configuration.py
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from unittest.mock import MagicMock
|
|
||||||
import pytest
|
import pytest
|
||||||
from jsonschema import ValidationError
|
from jsonschema import ValidationError
|
||||||
|
|
||||||
@ -19,10 +18,12 @@ def test_configuration_object() -> None:
|
|||||||
"""
|
"""
|
||||||
Test the Constants object has the mandatory Constants
|
Test the Constants object has the mandatory Constants
|
||||||
"""
|
"""
|
||||||
assert hasattr(Configuration, '_load_config')
|
assert hasattr(Configuration, 'load_config')
|
||||||
assert hasattr(Configuration, '_load_config_file')
|
assert hasattr(Configuration, '_load_config_file')
|
||||||
assert hasattr(Configuration, '_validate_config')
|
assert hasattr(Configuration, '_validate_config')
|
||||||
assert hasattr(Configuration, 'show_info')
|
assert hasattr(Configuration, '_load_common_config')
|
||||||
|
assert hasattr(Configuration, '_load_backtesting_config')
|
||||||
|
assert hasattr(Configuration, '_load_hyperopt_config')
|
||||||
assert hasattr(Configuration, 'get_config')
|
assert hasattr(Configuration, 'get_config')
|
||||||
|
|
||||||
|
|
||||||
@ -30,11 +31,6 @@ def test_load_config_invalid_pair(default_conf, mocker) -> None:
|
|||||||
"""
|
"""
|
||||||
Test the configuration validator with an invalid PAIR format
|
Test the configuration validator with an invalid PAIR format
|
||||||
"""
|
"""
|
||||||
mocker.patch.multiple(
|
|
||||||
'freqtrade.configuration.Configuration',
|
|
||||||
_load_config=MagicMock(return_value=[]),
|
|
||||||
show_info=MagicMock
|
|
||||||
)
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['exchange']['pair_whitelist'].append('BTC-ETH')
|
conf['exchange']['pair_whitelist'].append('BTC-ETH')
|
||||||
|
|
||||||
@ -47,11 +43,6 @@ def test_load_config_missing_attributes(default_conf, mocker) -> None:
|
|||||||
"""
|
"""
|
||||||
Test the configuration validator with a missing attribute
|
Test the configuration validator with a missing attribute
|
||||||
"""
|
"""
|
||||||
mocker.patch.multiple(
|
|
||||||
'freqtrade.configuration.Configuration',
|
|
||||||
_load_config=MagicMock(return_value=[]),
|
|
||||||
show_info=MagicMock
|
|
||||||
)
|
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf.pop('exchange')
|
conf.pop('exchange')
|
||||||
|
|
||||||
@ -64,11 +55,6 @@ def test_load_config_file(default_conf, mocker, caplog) -> None:
|
|||||||
"""
|
"""
|
||||||
Test Configuration._load_config_file() method
|
Test Configuration._load_config_file() method
|
||||||
"""
|
"""
|
||||||
mocker.patch.multiple(
|
|
||||||
'freqtrade.configuration.Configuration',
|
|
||||||
_load_config=MagicMock(return_value=[]),
|
|
||||||
show_info=MagicMock
|
|
||||||
)
|
|
||||||
file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
file_mock = mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
read_data=json.dumps(default_conf)
|
read_data=json.dumps(default_conf)
|
||||||
))
|
))
|
||||||
@ -83,7 +69,7 @@ def test_load_config_file(default_conf, mocker, caplog) -> None:
|
|||||||
|
|
||||||
def test_load_config(default_conf, mocker) -> None:
|
def test_load_config(default_conf, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test Configuration._load_config() without any cli params
|
Test Configuration.load_config() without any cli params
|
||||||
"""
|
"""
|
||||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
read_data=json.dumps(default_conf)
|
read_data=json.dumps(default_conf)
|
||||||
@ -91,7 +77,7 @@ def test_load_config(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
args = Arguments([], '').get_parsed_arg()
|
args = Arguments([], '').get_parsed_arg()
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
validated_conf = configuration._load_config()
|
validated_conf = configuration.load_config()
|
||||||
|
|
||||||
assert 'strategy' in validated_conf
|
assert 'strategy' in validated_conf
|
||||||
assert validated_conf['strategy'] == 'default_strategy'
|
assert validated_conf['strategy'] == 'default_strategy'
|
||||||
@ -101,7 +87,7 @@ def test_load_config(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
def test_load_config_with_params(default_conf, mocker) -> None:
|
def test_load_config_with_params(default_conf, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test Configuration._load_config() with cli params used
|
Test Configuration.load_config() with cli params used
|
||||||
"""
|
"""
|
||||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
read_data=json.dumps(default_conf)
|
read_data=json.dumps(default_conf)
|
||||||
@ -115,7 +101,7 @@ def test_load_config_with_params(default_conf, mocker) -> None:
|
|||||||
args = Arguments(args, '').get_parsed_arg()
|
args = Arguments(args, '').get_parsed_arg()
|
||||||
|
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
validated_conf = configuration._load_config()
|
validated_conf = configuration.load_config()
|
||||||
|
|
||||||
assert 'dynamic_whitelist' in validated_conf
|
assert 'dynamic_whitelist' in validated_conf
|
||||||
assert validated_conf['dynamic_whitelist'] == 10
|
assert validated_conf['dynamic_whitelist'] == 10
|
||||||
@ -141,22 +127,28 @@ def test_show_info(default_conf, mocker, caplog) -> None:
|
|||||||
args = Arguments(args, '').get_parsed_arg()
|
args = Arguments(args, '').get_parsed_arg()
|
||||||
|
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
configuration.show_info()
|
configuration.get_config()
|
||||||
|
|
||||||
assert tt.log_has(
|
assert tt.log_has(
|
||||||
'Using dynamically generated whitelist. (--dynamic-whitelist detected)',
|
'Parameter --dynamic-whitelist detected. '
|
||||||
|
'Using dynamically generated whitelist. '
|
||||||
|
'(not applicable with Backtesting and Hyperopt)',
|
||||||
caplog.record_tuples
|
caplog.record_tuples
|
||||||
)
|
)
|
||||||
|
|
||||||
assert tt.log_has(
|
assert tt.log_has(
|
||||||
'Dry_run will use the DB file: "tradesv3.dry_run.sqlite". '
|
'Parameter --dry-run-db detected ...',
|
||||||
'(--dry_run_db detected)',
|
caplog.record_tuples
|
||||||
|
)
|
||||||
|
|
||||||
|
assert tt.log_has(
|
||||||
|
'Dry_run will use the DB file: "tradesv3.dry_run.sqlite"',
|
||||||
caplog.record_tuples
|
caplog.record_tuples
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test the Dry run condition
|
# Test the Dry run condition
|
||||||
configuration.config.update({'dry_run': False})
|
configuration.config.update({'dry_run': False})
|
||||||
configuration.show_info()
|
configuration._load_common_config(configuration.config)
|
||||||
assert tt.log_has(
|
assert tt.log_has(
|
||||||
'Dry run is disabled. (--dry_run_db ignored)',
|
'Dry run is disabled. (--dry_run_db ignored)',
|
||||||
caplog.record_tuples
|
caplog.record_tuples
|
||||||
@ -167,7 +159,6 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
|
|||||||
"""
|
"""
|
||||||
Test setup_configuration() function
|
Test setup_configuration() function
|
||||||
"""
|
"""
|
||||||
mocker.patch('freqtrade.configuration.Configuration.show_info', MagicMock)
|
|
||||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
read_data=json.dumps(default_conf)
|
read_data=json.dumps(default_conf)
|
||||||
))
|
))
|
||||||
@ -212,7 +203,6 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
|||||||
"""
|
"""
|
||||||
Test setup_configuration() function
|
Test setup_configuration() function
|
||||||
"""
|
"""
|
||||||
mocker.patch('freqtrade.configuration.Configuration.show_info', MagicMock)
|
|
||||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
read_data=json.dumps(default_conf)
|
read_data=json.dumps(default_conf)
|
||||||
))
|
))
|
||||||
|
@ -29,7 +29,7 @@ def test_parse_args_backtesting(mocker) -> None:
|
|||||||
|
|
||||||
def test_main_start_hyperopt(mocker) -> None:
|
def test_main_start_hyperopt(mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test that main() can start hyperopt.
|
Test that main() can start hyperopt
|
||||||
"""
|
"""
|
||||||
hyperopt_mock = mocker.patch('freqtrade.optimize.hyperopt.start', MagicMock())
|
hyperopt_mock = mocker.patch('freqtrade.optimize.hyperopt.start', MagicMock())
|
||||||
main(['hyperopt'])
|
main(['hyperopt'])
|
||||||
@ -61,7 +61,7 @@ def test_set_loggers() -> None:
|
|||||||
|
|
||||||
def test_main(mocker, caplog) -> None:
|
def test_main(mocker, caplog) -> None:
|
||||||
"""
|
"""
|
||||||
Test main() function.
|
Test main() function
|
||||||
In this test we are skipping the while True loop by throwing an exception.
|
In this test we are skipping the while True loop by throwing an exception.
|
||||||
"""
|
"""
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -73,9 +73,11 @@ def test_main(mocker, caplog) -> None:
|
|||||||
clean=MagicMock(),
|
clean=MagicMock(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
args = ['-c', 'config.json.example']
|
||||||
|
|
||||||
# Test Main + the KeyboardInterrupt exception
|
# Test Main + the KeyboardInterrupt exception
|
||||||
with pytest.raises(SystemExit) as pytest_wrapped_e:
|
with pytest.raises(SystemExit) as pytest_wrapped_e:
|
||||||
main([])
|
main(args)
|
||||||
tt.log_has('Starting freqtrade', caplog.record_tuples)
|
tt.log_has('Starting freqtrade', caplog.record_tuples)
|
||||||
tt.log_has('Got SIGINT, aborting ...', caplog.record_tuples)
|
tt.log_has('Got SIGINT, aborting ...', caplog.record_tuples)
|
||||||
assert pytest_wrapped_e.type == SystemExit
|
assert pytest_wrapped_e.type == SystemExit
|
||||||
@ -87,5 +89,5 @@ def test_main(mocker, caplog) -> None:
|
|||||||
MagicMock(side_effect=BaseException)
|
MagicMock(side_effect=BaseException)
|
||||||
)
|
)
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
main([])
|
main(args)
|
||||||
tt.log_has('Got fatal exception!', caplog.record_tuples)
|
tt.log_has('Got fatal exception!', caplog.record_tuples)
|
||||||
|
Loading…
Reference in New Issue
Block a user