diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index e96305993..585704b2d 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -58,6 +58,7 @@ class Configuration(object): config['internals'] = {} logger.info('Validating configuration ...') + self._validate_config_schema(config) self._validate_config(config) # Set strategy if not specified in config and or if it's non default @@ -291,7 +292,7 @@ class Configuration(object): return config - def _validate_config(self, conf: Dict[str, Any]) -> Dict[str, Any]: + def _validate_config_schema(self, conf: Dict[str, Any]) -> Dict[str, Any]: """ Validate the configuration follow the Config Schema :param conf: Config in JSON format @@ -309,6 +310,35 @@ class Configuration(object): best_match(Draft4Validator(constants.CONF_SCHEMA).iter_errors(conf)).message ) + def _validate_config(self, conf: Dict[str, Any]) -> None: + """ + Validate the configuration consistency + :param conf: Config in JSON format + :return: Returns None if everything is ok, otherwise throw an exception + """ + + # validating trailing stoploss + self._validate_trailing_stoploss(conf) + + def _validate_trailing_stoploss(self, conf: Dict[str, Any]) -> None: + # Skip if trailing stoploss is not activated + if not conf.get('trailing_stop', False): + return + + tsl_positive = float(conf.get('trailing_stop_positive', 0)) + tsl_offset = float(conf.get('trailing_stop_positive_offset', 0)) + tsl_only_offset = conf.get('trailing_only_offset_is_reached', False) + + if tsl_only_offset: + if tsl_positive == 0.0: + raise OperationalException( + f'The config trailing_only_offset_is_reached need ' + 'trailing_stop_positive_offset to be more than 0 in your config.') + if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive: + raise OperationalException( + f'The config trailing_stop_positive_offset need ' + 'to be greater than trailing_stop_positive_offset in your config.') + def get_config(self) -> Dict[str, Any]: """ Return the config. Use this method to get the bot config diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index c71c1580f..7c757df09 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -449,33 +449,6 @@ def test_validate_order_types(default_conf, mocker): Exchange(default_conf) -def test_validate_tsl(default_conf, mocker): - api_mock = MagicMock() - mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) - mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) - mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) - mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') - default_conf['trailing_stop'] = True - default_conf['trailing_stop_positive'] = 0 - default_conf['trailing_stop_positive_offset'] = 0 - default_conf['trailing_only_offset_is_reached'] = False - - Exchange(default_conf) - - default_conf['trailing_only_offset_is_reached'] = True - with pytest.raises(OperationalException, - match=r'The config trailing_only_offset_is_reached need ' - 'trailing_stop_positive_offset to be more than 0 in your config.'): - Exchange(default_conf) - - default_conf['trailing_stop_positive_offset'] = 0.01 - default_conf['trailing_stop_positive'] = 0.015 - with pytest.raises(OperationalException, - match=r'The config trailing_stop_positive_offset need ' - 'to be greater than trailing_stop_positive_offset in your config.'): - Exchange(default_conf) - - def test_validate_order_types_not_in_config(default_conf, mocker): api_mock = MagicMock() mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index 51098baaa..3b7b1285c 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -13,6 +13,7 @@ from freqtrade import OperationalException, constants from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration, set_loggers from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL +from freqtrade.exchange import Exchange from freqtrade.state import RunMode from freqtrade.tests.conftest import log_has @@ -22,7 +23,7 @@ def test_load_config_invalid_pair(default_conf) -> None: with pytest.raises(ValidationError, match=r'.*does not match.*'): configuration = Configuration(Namespace()) - configuration._validate_config(default_conf) + configuration._validate_config_schema(default_conf) def test_load_config_missing_attributes(default_conf) -> None: @@ -30,7 +31,7 @@ def test_load_config_missing_attributes(default_conf) -> None: with pytest.raises(ValidationError, match=r'.*\'exchange\' is a required property.*'): configuration = Configuration(Namespace()) - configuration._validate_config(default_conf) + configuration._validate_config_schema(default_conf) def test_load_config_incorrect_stake_amount(default_conf) -> None: @@ -38,7 +39,7 @@ def test_load_config_incorrect_stake_amount(default_conf) -> None: with pytest.raises(ValidationError, match=r'.*\'fake\' does not match \'unlimited\'.*'): configuration = Configuration(Namespace()) - configuration._validate_config(default_conf) + configuration._validate_config_schema(default_conf) def test_load_config_file(default_conf, mocker, caplog) -> None: @@ -573,3 +574,30 @@ def test__create_datadir(mocker, default_conf, caplog) -> None: cfg._create_datadir(default_conf, '/foo/bar') assert md.call_args[0][0] == "/foo/bar" assert log_has('Created data directory: /foo/bar', caplog.record_tuples) + + +def test_validate_tsl(default_conf, mocker): + mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={})) + mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex') + default_conf['trailing_stop'] = True + default_conf['trailing_stop_positive'] = 0 + default_conf['trailing_stop_positive_offset'] = 0 + default_conf['trailing_only_offset_is_reached'] = False + + Exchange(default_conf) + + default_conf['trailing_only_offset_is_reached'] = True + with pytest.raises(OperationalException, + match=r'The config trailing_only_offset_is_reached need ' + 'trailing_stop_positive_offset to be more than 0 in your config.'): + Exchange(default_conf) + + default_conf['trailing_stop_positive_offset'] = 0.01 + default_conf['trailing_stop_positive'] = 0.015 + with pytest.raises(OperationalException, + match=r'The config trailing_stop_positive_offset need ' + 'to be greater than trailing_stop_positive_offset in your config.'): + Exchange(default_conf)