From 79e70016e0a2133956a0056ba759b0189f478c3e Mon Sep 17 00:00:00 2001 From: "yakovenko.dima" Date: Sat, 3 Apr 2021 15:46:27 -0300 Subject: [PATCH] add "cfg" argument for overriding any config from command line --- freqtrade/commands/arguments.py | 2 +- freqtrade/commands/cli_options.py | 55 +++++++++++++++++++++++- freqtrade/configuration/configuration.py | 3 ++ tests/commands/test_commands.py | 24 +++++++++++ 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 9468a7f7d..d21fe1277 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -10,7 +10,7 @@ from freqtrade.commands.cli_options import AVAILABLE_CLI_OPTIONS from freqtrade.constants import DEFAULT_CONFIG -ARGS_COMMON = ["verbosity", "logfile", "version", "config", "datadir", "user_data_dir"] +ARGS_COMMON = ["verbosity", "logfile", "version", "config", "cfg", "datadir", "user_data_dir"] ARGS_STRATEGY = ["strategy", "strategy_path"] diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 12c03d824..304fe3468 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -1,6 +1,7 @@ """ Definition of cli arguments used in arguments.py """ +import argparse from argparse import ArgumentTypeError from freqtrade import __version__, constants @@ -38,6 +39,50 @@ class Arg: self.kwargs = kwargs +class SetDictFromArgAction(argparse.Action): + """ + argparse action to split an argument into KEY=VALUE form + on the first = and append to a dictionary. + """ + + def __call__(self, parser, args, values, option_string=None): + for key_value in values: + try: + (k, v) = key_value.split("=", 2) + except ValueError as ex: + raise argparse.ArgumentError(self, f"could not parse argument \"{values[0]}\" as k=v format") + d = self.set_dict_path_value(getattr(args, self.dest) or {}, k, v) + setattr(args, self.dest, d) + + @staticmethod + def set_dict_path_value(obj, key, value): + root_obj = obj + keys = key.split('.') + latest = keys.pop() + for k in keys: + obj[k] = obj.get(k, {}) + obj = obj[k] + latest_key, *value_type = latest.split(':') + value_type = value_type[0] if len(value_type) > 0 else None + if value_type is None or value_type == "str": + value = value + elif value_type == "int": + value = int(value) + elif value_type == "float": + value = float(value) + elif value_type == "bool": + if value == "True" or value == "true" or value == "1": + value = True + elif value == "False" or value == "false" or value == "0": + value = False + else: + raise ArgumentTypeError(f"Argument '{key}' has unknown value '{value}'. Must be (true|false|True|False|1|0)") + else: + raise Exception(f"Unsupported arg type '{value_type}'") + obj[latest_key] = value + return root_obj + + # List of available command line options AVAILABLE_CLI_OPTIONS = { # Common options @@ -530,4 +575,12 @@ AVAILABLE_CLI_OPTIONS = { help='Do not print epoch details header.', action='store_true', ), -} + "cfg": Arg( + '--cfg', + nargs='+', + metavar="KEY=VALUE", + action=SetDictFromArgAction, + help='Set any config value.', + # action='store_const' + ), +} \ No newline at end of file diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index a40a4fd83..9deb6c391 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -93,6 +93,9 @@ class Configuration: # Load all configs config: Dict[str, Any] = self.load_from_files(self.args.get("config", [])) + if 'cfg' in self.args: + config = deep_merge_dicts(self.args['cfg'] or {}, config) + # Keep a copy of the original configuration file config['original_config'] = deepcopy(config) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index e21ef4dd1..50762a40f 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -35,6 +35,30 @@ def test_setup_utils_configuration(): assert config['exchange']['secret'] == '' +def test_cfg_argument_replaces_configuration(): + args = [ + 'list-exchanges', '--config', 'config_bittrex.json.example', + ] + + # Check original config + config = setup_utils_configuration(get_args(args), RunMode.OTHER) + assert config['dry_run'] is True + assert config['exchange']['key'] == '' + assert config['stake_amount'] == 0.05 + assert config['timeframe'] == '5m' + + args = [ + 'list-exchanges', '--config', 'config_bittrex.json.example', + '--cfg', 'stake_amount:float=0.1', 'timeframe=15m', 'exchange.sandbox:bool=true', + ] + + # Check original config + config = setup_utils_configuration(get_args(args), RunMode.OTHER) + assert config['stake_amount'] == 0.1 + assert config['timeframe'] == '15m' + assert config['exchange']['sandbox'] is True + + def test_start_trading_fail(mocker, caplog): mocker.patch("freqtrade.worker.Worker.run", MagicMock(side_effect=OperationalException))