From 35253f9c8d9463432d815f4f6524e3b770217a6b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 19 Mar 2024 18:19:22 +0100 Subject: [PATCH 01/14] Add overrides for Exchange Support --- freqtrade/exchange/exchange.py | 4 ++++ tests/exchange/test_exchange.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 84b4212bd..2bf92c389 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -88,6 +88,8 @@ class Exchange: "order_props_in_contracts": ['amount', 'filled', 'remaining'], # Override createMarketBuyOrderRequiresPrice where ccxt has it wrong "marketOrderRequiresPrice": False, + "exchange_has_overrides": {}, # Dictionary overriding ccxt's "has". + # Expected to be in the format {"fetchOHLCV": True} or {"fetchOHLCV": False} } _ft_has: Dict = {} _ft_has_futures: Dict = {} @@ -717,6 +719,8 @@ class Exchange: :param endpoint: Name of endpoint (e.g. 'fetchOHLCV', 'fetchTickers') :return: bool """ + if endpoint in self._ft_has.get('exchange_has_overrides', {}): + return self._ft_has['exchange_has_overrides'][endpoint] return endpoint in self._api.has and self._api.has[endpoint] def get_precision_amount(self, pair: str) -> Optional[float]: diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 34d4ca4c6..104730d8c 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1066,6 +1066,9 @@ def test_exchange_has(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock) assert not exchange.exchange_has("deadbeef") + exchange._ft_has['exchange_has_overrides'] = {'deadbeef': True} + assert exchange.exchange_has("deadbeef") + @pytest.mark.parametrize("side,leverage", [ ("buy", 1), From 21dcb4a6a15d903e243001e9293fb3433a59efb4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 14 Mar 2024 20:31:34 +0100 Subject: [PATCH 02/14] update setup_utils_configuration to not force-set dry-run mode --- freqtrade/configuration/config_setup.py | 6 ++++-- tests/commands/test_commands.py | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/freqtrade/configuration/config_setup.py b/freqtrade/configuration/config_setup.py index d49bf61f6..154ba5175 100644 --- a/freqtrade/configuration/config_setup.py +++ b/freqtrade/configuration/config_setup.py @@ -10,7 +10,8 @@ from .configuration import Configuration logger = logging.getLogger(__name__) -def setup_utils_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str, Any]: +def setup_utils_configuration( + args: Dict[str, Any], method: RunMode, *, set_dry: bool = True) -> Dict[str, Any]: """ Prepare the configuration for utils subcommands :param args: Cli args from Arguments() @@ -21,7 +22,8 @@ def setup_utils_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str config = configuration.get_config() # Ensure these modes are using Dry-run - config['dry_run'] = True + if set_dry: + config['dry_run'] = True validate_config_consistency(config, preliminary=True) return config diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 2252ff9f4..72442d5d5 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -39,6 +39,10 @@ def test_setup_utils_configuration(): assert "exchange" in config assert config['dry_run'] is True + config = setup_utils_configuration(get_args(args), RunMode.OTHER, set_dry=False) + assert "exchange" in config + assert config['dry_run'] is False + def test_start_trading_fail(mocker, caplog): From 8e5097d9a96cbfee94e1a973cdd47371a06cf928 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 14 Mar 2024 20:34:39 +0100 Subject: [PATCH 03/14] Update setup-tools-config for non-dry mode --- tests/commands/test_commands.py | 4 ++++ tests/testdata/testconfigs/test_base_config.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 72442d5d5..d20d37c0e 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -39,6 +39,10 @@ def test_setup_utils_configuration(): assert "exchange" in config assert config['dry_run'] is True + args = [ + 'list-exchanges', '--config', 'tests/testdata/testconfigs/testconfig.json', + ] + config = setup_utils_configuration(get_args(args), RunMode.OTHER, set_dry=False) assert "exchange" in config assert config['dry_run'] is False diff --git a/tests/testdata/testconfigs/test_base_config.json b/tests/testdata/testconfigs/test_base_config.json index d15c5890b..40a114150 100644 --- a/tests/testdata/testconfigs/test_base_config.json +++ b/tests/testdata/testconfigs/test_base_config.json @@ -1,6 +1,6 @@ { "stake_currency": "", - "dry_run": true, + "dry_run": false, "exchange": { "name": "", "key": "", From 7543b2681d94287f350259eba23424a5a827025c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 14 Mar 2024 20:39:34 +0100 Subject: [PATCH 04/14] Add show_config command --- freqtrade/commands/__init__.py | 2 +- freqtrade/commands/arguments.py | 15 ++++++++++++--- freqtrade/commands/build_config_commands.py | 12 ++++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/freqtrade/commands/__init__.py b/freqtrade/commands/__init__.py index 98e7cb084..e0fa0fd51 100644 --- a/freqtrade/commands/__init__.py +++ b/freqtrade/commands/__init__.py @@ -8,7 +8,7 @@ Note: Be careful with file-scoped imports in these subfiles. """ from freqtrade.commands.analyze_commands import start_analysis_entries_exits from freqtrade.commands.arguments import Arguments -from freqtrade.commands.build_config_commands import start_new_config +from freqtrade.commands.build_config_commands import start_new_config, start_show_config from freqtrade.commands.data_commands import (start_convert_data, start_convert_trades, start_download_data, start_list_data) from freqtrade.commands.db_commands import start_convert_db diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 191f07910..f93f596fd 100755 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -62,6 +62,7 @@ ARGS_TEST_PAIRLIST = ["user_data_dir", "verbosity", "config", "quote_currencies" ARGS_CREATE_USERDIR = ["user_data_dir", "reset"] ARGS_BUILD_CONFIG = ["config"] +ARGS_SHOW_CONFIG = ["user_data_dir", "config"] ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"] @@ -209,9 +210,9 @@ class Arguments: start_list_strategies, start_list_timeframes, start_lookahead_analysis, start_new_config, start_new_strategy, start_plot_dataframe, start_plot_profit, - start_recursive_analysis, start_show_trades, - start_strategy_update, start_test_pairlist, start_trading, - start_webserver) + start_recursive_analysis, start_show_config, + start_show_trades, start_strategy_update, + start_test_pairlist, start_trading, start_webserver) subparsers = self.parser.add_subparsers(dest='command', # Use custom message when no subhandler is added @@ -244,6 +245,14 @@ class Arguments: build_config_cmd.set_defaults(func=start_new_config) self._build_args(optionlist=ARGS_BUILD_CONFIG, parser=build_config_cmd) + # add show-config subcommand + show_config_cmd = subparsers.add_parser( + 'show-config', + help="Show resolved config", + ) + show_config_cmd.set_defaults(func=start_show_config) + self._build_args(optionlist=ARGS_SHOW_CONFIG, parser=show_config_cmd) + # add new-strategy subcommand build_strategy_cmd = subparsers.add_parser( 'new-strategy', diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 5b3447d70..16b85918b 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -5,9 +5,11 @@ from typing import Any, Dict, List from questionary import Separator, prompt +from freqtrade.configuration.config_setup import setup_utils_configuration from freqtrade.configuration.detect_environment import running_in_docker from freqtrade.configuration.directory_operations import chown_user_directory from freqtrade.constants import UNLIMITED_STAKE_AMOUNT +from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException from freqtrade.exchange import MAP_EXCHANGE_CHILDCLASS, available_exchanges from freqtrade.util import render_template @@ -264,3 +266,13 @@ def start_new_config(args: Dict[str, Any]) -> None: "Please delete it or use a different configuration file name.") selections = ask_user_config() deploy_new_config(config_path, selections) + + +def start_show_config(args: Dict[str, Any]) -> None: + + config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE, set_dry=False) + + # TODO: Sanitize from sensitive info before printing + + from rich import print + print(config) From 3059d66ece7fc58592703acdc20dd9556f830f33 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 19 Mar 2024 19:19:26 +0100 Subject: [PATCH 05/14] use original config for printing --- freqtrade/commands/build_config_commands.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 16b85918b..1f8192d44 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -274,5 +274,7 @@ def start_show_config(args: Dict[str, Any]) -> None: # TODO: Sanitize from sensitive info before printing - from rich import print - print(config) + print("Your combined configuration is:") + + from rich import print_json + print_json(data=config['original_config']) From 38ae3ac03c3610744ef2a3c91a48d0744b124594 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 19 Mar 2024 19:30:35 +0100 Subject: [PATCH 06/14] Add "sanitize config" section --- freqtrade/commands/build_config_commands.py | 5 +++- freqtrade/configuration/__init__.py | 1 + freqtrade/configuration/config_secrets.py | 33 +++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 freqtrade/configuration/config_secrets.py diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 1f8192d44..8d3162e5c 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -5,6 +5,7 @@ from typing import Any, Dict, List from questionary import Separator, prompt +from freqtrade.configuration import sanitize_config from freqtrade.configuration.config_setup import setup_utils_configuration from freqtrade.configuration.detect_environment import running_in_docker from freqtrade.configuration.directory_operations import chown_user_directory @@ -276,5 +277,7 @@ def start_show_config(args: Dict[str, Any]) -> None: print("Your combined configuration is:") + config_sanitized = sanitize_config(config['original_config']) + from rich import print_json - print_json(data=config['original_config']) + print_json(data=config_sanitized) diff --git a/freqtrade/configuration/__init__.py b/freqtrade/configuration/__init__.py index 05921606c..aa06a70c9 100644 --- a/freqtrade/configuration/__init__.py +++ b/freqtrade/configuration/__init__.py @@ -1,5 +1,6 @@ # flake8: noqa: F401 +from freqtrade.configuration.config_secrets import sanitize_config from freqtrade.configuration.config_setup import setup_utils_configuration from freqtrade.configuration.config_validation import validate_config_consistency from freqtrade.configuration.configuration import Configuration diff --git a/freqtrade/configuration/config_secrets.py b/freqtrade/configuration/config_secrets.py new file mode 100644 index 000000000..b076bfc2c --- /dev/null +++ b/freqtrade/configuration/config_secrets.py @@ -0,0 +1,33 @@ + + +from freqtrade.constants import Config + + +def sanitize_config(config: Config) -> Config: + """ + Remove sensitive information from the config. + :param config: Configuration + :return: Configuration + """ + keys_to_remove = [ + "exchange.key", + "exchange.secret", + "exchange.password", + "exchange.uid", + "telegram.token", + "telegram.chat_id", + "discord.webhook_url", + "api_server.password", + ] + + for key in keys_to_remove: + if '.' in key: + nested_keys = key.split('.') + nested_config = config + for nested_key in nested_keys[:-1]: + nested_config = nested_config.get(nested_key, {}) + nested_config[nested_keys[-1]] = 'REDACTED' + else: + config[key] = 'REDACTED' + + return config From 6b7935f1ae0e0c028d66017df6a3356c43c54745 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 19 Mar 2024 20:23:51 +0100 Subject: [PATCH 07/14] Add initial test ... --- tests/commands/test_commands.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index d20d37c0e..8e59ea2c2 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -12,9 +12,9 @@ from freqtrade.commands import (start_backtesting_show, start_convert_data, star start_create_userdir, start_download_data, start_hyperopt_list, start_hyperopt_show, start_install_ui, start_list_data, start_list_exchanges, start_list_markets, start_list_strategies, - start_list_timeframes, start_new_strategy, start_show_trades, - start_strategy_update, start_test_pairlist, start_trading, - start_webserver) + start_list_timeframes, start_new_strategy, start_show_config, + start_show_trades, start_strategy_update, start_test_pairlist, + start_trading, start_webserver) from freqtrade.commands.db_commands import start_convert_db from freqtrade.commands.deploy_commands import (clean_ui_subdir, download_and_install_ui, get_ui_download_url, read_ui_version) @@ -1579,3 +1579,18 @@ def test_start_strategy_updater(mocker, tmp_path): start_strategy_update(pargs) # Number of strategies in the test directory assert sc_mock.call_count == 2 + + +def test_start_show_config(capsys): + args = [ + "show-config", + "--config", + "tests/testdata/testconfigs/main_test_config.json", + ] + pargs = get_args(args) + start_show_config(pargs) + + captured = capsys.readouterr() + assert "Your combined configuration is:" in captured.out + assert '"max_open_trades":' in captured.out + assert '"secret": "REDACTED"' in captured.out From 81d1a662a28e64641b778ae3ca9a82bf48371eac Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Mar 2024 07:06:24 +0100 Subject: [PATCH 08/14] Add --show-sensitive CLI option --- freqtrade/commands/arguments.py | 2 +- freqtrade/commands/build_config_commands.py | 6 ++++-- freqtrade/commands/cli_options.py | 6 ++++++ freqtrade/configuration/config_secrets.py | 5 ++++- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index f93f596fd..55c3aa586 100755 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -62,7 +62,7 @@ ARGS_TEST_PAIRLIST = ["user_data_dir", "verbosity", "config", "quote_currencies" ARGS_CREATE_USERDIR = ["user_data_dir", "reset"] ARGS_BUILD_CONFIG = ["config"] -ARGS_SHOW_CONFIG = ["user_data_dir", "config"] +ARGS_SHOW_CONFIG = ["user_data_dir", "config", "show_sensitive"] ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"] diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 8d3162e5c..63862c3bf 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -276,8 +276,10 @@ def start_show_config(args: Dict[str, Any]) -> None: # TODO: Sanitize from sensitive info before printing print("Your combined configuration is:") - - config_sanitized = sanitize_config(config['original_config']) + config_sanitized = sanitize_config( + config['original_config'], + show_sensitive=args.get('show_sensitive', False) + ) from rich import print_json print_json(data=config_sanitized) diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 4d2dcb1fe..287ab2594 100755 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -716,4 +716,10 @@ AVAILABLE_CLI_OPTIONS = { help='Specify startup candles to be checked (`199`, `499`, `999`, `1999`).', nargs='+', ), + "show_sensitive": Arg( + '--show-sensitive', + help='Show secrets in the output.', + action='store_true', + default=False, + ), } diff --git a/freqtrade/configuration/config_secrets.py b/freqtrade/configuration/config_secrets.py index b076bfc2c..5f4de53e7 100644 --- a/freqtrade/configuration/config_secrets.py +++ b/freqtrade/configuration/config_secrets.py @@ -3,12 +3,15 @@ from freqtrade.constants import Config -def sanitize_config(config: Config) -> Config: +def sanitize_config(config: Config, *, show_sensitive: bool = False) -> Config: """ Remove sensitive information from the config. :param config: Configuration + :param show_sensitive: Show sensitive information :return: Configuration """ + if show_sensitive: + return config keys_to_remove = [ "exchange.key", "exchange.secret", From 6dc4fa9dbffaa6610e826e162032ffe622c630ae Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Mar 2024 07:07:00 +0100 Subject: [PATCH 09/14] Add test for show-sensitive --- tests/commands/test_commands.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 8e59ea2c2..a0908bd78 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -1594,3 +1594,17 @@ def test_start_show_config(capsys): assert "Your combined configuration is:" in captured.out assert '"max_open_trades":' in captured.out assert '"secret": "REDACTED"' in captured.out + + args = [ + "show-config", + "--config", + "tests/testdata/testconfigs/main_test_config.json", + "--show-sensitive" + ] + pargs = get_args(args) + start_show_config(pargs) + + captured = capsys.readouterr() + assert "Your combined configuration is:" in captured.out + assert '"max_open_trades":' in captured.out + assert '"secret": "REDACTED"' not in captured.out From b1fe5b6d8aa4665e657cfdd8565f81fc69c07fb5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Mar 2024 07:12:14 +0100 Subject: [PATCH 10/14] Add "sensitive information" warning output --- freqtrade/configuration/configuration.py | 6 ++++++ tests/commands/test_commands.py | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 58b1fdf84..0da307175 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -200,6 +200,12 @@ class Configuration: config['exportfilename'] = (config['user_data_dir'] / 'backtest_results') + if self.args.get('show_sensitive'): + logger.warning( + "Sensitive information will be shown in the upcomming output. " + "Please make sure to never share this output without redacting " + "the information yourself.") + def _process_optimize_options(self, config: Config) -> None: # This will override the strategy configuration diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index a0908bd78..9b0451f88 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -1581,7 +1581,7 @@ def test_start_strategy_updater(mocker, tmp_path): assert sc_mock.call_count == 2 -def test_start_show_config(capsys): +def test_start_show_config(capsys, caplog): args = [ "show-config", "--config", @@ -1608,3 +1608,4 @@ def test_start_show_config(capsys): assert "Your combined configuration is:" in captured.out assert '"max_open_trades":' in captured.out assert '"secret": "REDACTED"' not in captured.out + assert log_has_re(r'Sensitive information will be shown in the upcomming output.*', caplog) From 82ff4d58798e16ed0364df39eb1b828151924411 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Mar 2024 07:15:02 +0100 Subject: [PATCH 11/14] Add explicit test for config sanitization --- tests/test_configuration.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 71585cac3..b909305f3 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -10,6 +10,7 @@ from jsonschema import ValidationError from freqtrade.commands import Arguments from freqtrade.configuration import Configuration, validate_config_consistency +from freqtrade.configuration.config_secrets import sanitize_config from freqtrade.configuration.config_validation import validate_config_schema from freqtrade.configuration.deprecated_settings import (check_conflicting_settings, process_deprecated_setting, @@ -1426,7 +1427,7 @@ def test_flat_vars_to_nested_dict(caplog): assert not log_has("Loading variable 'NOT_RELEVANT'", caplog) -def test_setup_hyperopt_freqai(mocker, default_conf, caplog) -> None: +def test_setup_hyperopt_freqai(mocker, default_conf) -> None: patched_configuration_load_config_file(mocker, default_conf) mocker.patch( 'freqtrade.configuration.configuration.create_datadir', @@ -1459,7 +1460,7 @@ def test_setup_hyperopt_freqai(mocker, default_conf, caplog) -> None: validate_config_consistency(config) -def test_setup_freqai_backtesting(mocker, default_conf, caplog) -> None: +def test_setup_freqai_backtesting(mocker, default_conf) -> None: patched_configuration_load_config_file(mocker, default_conf) mocker.patch( 'freqtrade.configuration.configuration.create_datadir', @@ -1506,3 +1507,14 @@ def test_setup_freqai_backtesting(mocker, default_conf, caplog) -> None: OperationalException, match=r".* pass --timerange if you intend to use FreqAI .*" ): validate_config_consistency(conf) + + +def test_sanitize_config(default_conf_usdt): + res = sanitize_config(default_conf_usdt) + + assert res['exchange']['key'] == 'REDACTED' + assert res['exchange']['secret'] == 'REDACTED' + + res = sanitize_config(default_conf_usdt, show_sensitive=True) + assert res['exchange']['key'] == default_conf_usdt['exchange']['key'] + assert res['exchange']['secret'] == default_conf_usdt['exchange']['secret'] From 65105f7768a7c9f2dc4ecd6942d2658b677064d6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 20 Mar 2024 07:22:12 +0100 Subject: [PATCH 12/14] Don't modify original config --- freqtrade/configuration/config_secrets.py | 4 ++-- tests/test_configuration.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/freqtrade/configuration/config_secrets.py b/freqtrade/configuration/config_secrets.py index 5f4de53e7..47ee741bf 100644 --- a/freqtrade/configuration/config_secrets.py +++ b/freqtrade/configuration/config_secrets.py @@ -1,4 +1,4 @@ - +from copy import deepcopy from freqtrade.constants import Config @@ -22,7 +22,7 @@ def sanitize_config(config: Config, *, show_sensitive: bool = False) -> Config: "discord.webhook_url", "api_server.password", ] - + config = deepcopy(config) for key in keys_to_remove: if '.' in key: nested_keys = key.split('.') diff --git a/tests/test_configuration.py b/tests/test_configuration.py index b909305f3..124258c04 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1510,7 +1510,10 @@ def test_setup_freqai_backtesting(mocker, default_conf) -> None: def test_sanitize_config(default_conf_usdt): + assert default_conf_usdt['exchange']['key'] != 'REDACTED' res = sanitize_config(default_conf_usdt) + # Didn't modify original dict + assert default_conf_usdt['exchange']['key'] != 'REDACTED' assert res['exchange']['key'] == 'REDACTED' assert res['exchange']['secret'] == 'REDACTED' From d0bda7c2ce5db654c77965921dcda5d2683a9186 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Mar 2024 07:14:58 +0100 Subject: [PATCH 13/14] Add documentation for show-config utility command --- docs/assets/show-config-output.png | Bin 0 -> 29963 bytes docs/utils.md | 47 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 docs/assets/show-config-output.png diff --git a/docs/assets/show-config-output.png b/docs/assets/show-config-output.png new file mode 100644 index 0000000000000000000000000000000000000000..010c5b8a4733837675b69fab96e0528a3b33432b GIT binary patch literal 29963 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 literal 0 HcmV?d00001 diff --git a/docs/utils.md b/docs/utils.md index ea52737d6..a11e7b698 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -66,6 +66,53 @@ $ freqtrade new-config --config user_data/config_binance.json ? Do you want to enable Telegram? No ``` +## Show config + +Show configuration file (with sensitive values redacted by default). +Especially useful with [split configuration files](configuration.md#multiple-configuration-files) or [environment variables](configuration.md#environment-variables), where this command will show the merged configuration. + +![Show config output](assets/show-config-output.png) + +``` +usage: freqtrade show-config [-h] [--userdir PATH] [-c PATH] + [--show-sensitive] + +options: + -h, --help show this help message and exit + --userdir PATH, --user-data-dir PATH + Path to userdata directory. + -c PATH, --config PATH + Specify configuration file (default: + `userdir/config.json` or `config.json` whichever + exists). Multiple --config options may be used. Can be + set to `-` to read config from stdin. + --show-sensitive Show secrets in the output. +``` + +``` output +Your combined configuration is: +{ + "exit_pricing": { + "price_side": "other", + "use_order_book": true, + "order_book_top": 1 + }, + "stake_currency": "USDT", + "exchange": { + "name": "binance", + "key": "REDACTED", + "secret": "REDACTED", + "ccxt_config": {}, + "ccxt_async_config": {}, + } + // ... +} +``` + +!!! Warning "Sharing information provided by this command" + We try to remove all known sensitive information from the default output (without `--show-sensitive`). + Yet, please do double-check for sensitive values in your output to make sure you're not accidentally exposing some private info. + ## Create new strategy Creates a new strategy from a template similar to SampleStrategy. From b4c9541fb82d61fd8762952528992989cc83bae7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 21 Mar 2024 07:24:17 +0100 Subject: [PATCH 14/14] Add hints about show-config to configuration docs --- docs/configuration.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 51c97ae80..f9348e497 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -49,6 +49,9 @@ FREQTRADE__EXCHANGE__SECRET= !!! Note Environment variables detected are logged at startup - so if you can't find why a value is not what you think it should be based on the configuration, make sure it's not loaded from an environment variable. +!!! Tip "Validate combined result" + You can use the [show-config subcommand](utils.md#show-config) to see the final, combined configuration. + ### Multiple configuration files Multiple configuration files can be specified and used by the bot or the bot can read its configuration parameters from the process standard input stream. @@ -56,6 +59,9 @@ Multiple configuration files can be specified and used by the bot or the bot can You can specify additional configuration files in `add_config_files`. Files specified in this parameter will be loaded and merged with the initial config file. The files are resolved relative to the initial configuration file. This is similar to using multiple `--config` parameters, but simpler in usage as you don't have to specify all files for all commands. +!!! Tip "Validate combined result" + You can use the [show-config subcommand](utils.md#show-config) to see the final, combined configuration. + !!! Tip "Use multiple configuration files to keep secrets secret" You can use a 2nd configuration file containing your secrets. That way you can share your "primary" configuration file, while still keeping your API keys for yourself. The 2nd file should only specify what you intend to override.