freqtrade_origin/tests/test_configuration.py

1621 lines
54 KiB
Python
Raw Normal View History

2018-07-29 14:09:44 +00:00
# pragma pylint: disable=missing-docstring, protected-access, invalid-name
import json
import warnings
from copy import deepcopy
2019-03-29 19:16:52 +00:00
from pathlib import Path
from unittest.mock import MagicMock
2018-03-17 21:44:47 +00:00
import pytest
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
2024-05-12 13:08:40 +00:00
from freqtrade.configuration.deprecated_settings import (
check_conflicting_settings,
process_deprecated_setting,
process_removed_setting,
process_temporary_deprecated_settings,
)
from freqtrade.configuration.environment_vars import _flat_vars_to_nested_dict
2024-05-12 13:08:40 +00:00
from freqtrade.configuration.load_config import (
load_config_file,
load_file,
load_from_files,
log_config_error_range,
)
2021-07-31 15:43:10 +00:00
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_VAR_PREFIX
2021-06-08 19:20:35 +00:00
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
2024-05-12 13:08:40 +00:00
from tests.conftest import (
CURRENT_TEST_STRATEGY,
log_has,
log_has_re,
patched_configuration_load_config_file,
)
@pytest.fixture(scope="function")
def all_conf():
config_file = Path(__file__).parents[1] / "config_examples/config_full.example.json"
2019-08-10 12:15:09 +00:00
conf = load_config_file(str(config_file))
return conf
2018-03-30 20:14:35 +00:00
def test_load_config_missing_attributes(default_conf) -> None:
conf = deepcopy(default_conf)
conf.pop("exchange")
with pytest.raises(ValidationError, match=r".*'exchange' is a required property.*"):
validate_config_schema(conf)
conf = deepcopy(default_conf)
conf.pop("stake_currency")
conf["runmode"] = RunMode.DRY_RUN
with pytest.raises(ValidationError, match=r".*'stake_currency' is a required property.*"):
validate_config_schema(conf)
2018-06-03 22:48:26 +00:00
def test_load_config_incorrect_stake_amount(default_conf) -> None:
default_conf["stake_amount"] = "fake"
2018-06-03 22:48:26 +00:00
with pytest.raises(ValidationError, match=r".*'fake' does not match 'unlimited'.*"):
validate_config_schema(default_conf)
2018-06-03 22:48:26 +00:00
def test_load_config_file(default_conf, mocker, caplog) -> None:
del default_conf["user_data_dir"]
default_conf["datadir"] = str(default_conf["datadir"])
file_mock = mocker.patch(
"freqtrade.configuration.load_config.Path.open",
mocker.mock_open(read_data=json.dumps(default_conf)),
)
validated_conf = load_config_file("somefile")
assert file_mock.call_count == 1
assert validated_conf.items() >= default_conf.items()
2020-03-22 19:15:33 +00:00
def test_load_config_file_error(default_conf, mocker, caplog) -> None:
del default_conf["user_data_dir"]
default_conf["datadir"] = str(default_conf["datadir"])
filedata = json.dumps(default_conf).replace('"stake_amount": 0.001,', '"stake_amount": .001,')
mocker.patch(
"freqtrade.configuration.load_config.Path.open", mocker.mock_open(read_data=filedata)
)
2020-03-22 19:15:33 +00:00
mocker.patch.object(Path, "read_text", MagicMock(return_value=filedata))
with pytest.raises(OperationalException, match=r".*Please verify the following segment.*"):
load_config_file("somefile")
2020-03-22 19:15:33 +00:00
2020-03-23 06:54:27 +00:00
def test_load_config_file_error_range(default_conf, mocker, caplog) -> None:
del default_conf["user_data_dir"]
default_conf["datadir"] = str(default_conf["datadir"])
filedata = json.dumps(default_conf).replace('"stake_amount": 0.001,', '"stake_amount": .001,')
2020-03-23 06:54:27 +00:00
mocker.patch.object(Path, "read_text", MagicMock(return_value=filedata))
x = log_config_error_range("somefile", "Parse error at offset 64: Invalid value.")
2020-03-23 06:54:27 +00:00
assert isinstance(x, str)
assert (
x == '{"max_open_trades": 1, "stake_currency": "BTC", '
'"stake_amount": .001, "fiat_display_currency": "USD", '
'"timeframe": "5m", "dry_run": true, "cance'
)
2020-03-23 06:54:27 +00:00
2021-05-30 18:07:57 +00:00
filedata = json.dumps(default_conf, indent=2).replace(
'"stake_amount": 0.001,', '"stake_amount": .001,'
)
2021-05-30 18:07:57 +00:00
mocker.patch.object(Path, "read_text", MagicMock(return_value=filedata))
x = log_config_error_range("somefile", "Parse error at offset 4: Invalid value.")
2021-05-30 18:07:57 +00:00
assert isinstance(x, str)
assert x == ' "max_open_trades": 1,\n "stake_currency": "BTC",\n' ' "stake_amount": .001,'
2021-05-30 18:07:57 +00:00
x = log_config_error_range("-", "")
assert x == ""
2021-05-30 18:07:57 +00:00
2020-03-23 06:54:27 +00:00
2023-11-05 15:15:36 +00:00
def test_load_file_error(tmp_path):
testpath = tmp_path / "config.json"
2021-05-30 18:14:41 +00:00
with pytest.raises(OperationalException, match=r"File .* not found!"):
load_file(testpath)
def test__args_to_config(caplog):
arg_list = ["trade", "--strategy-path", "TestTest"]
2019-09-04 14:38:33 +00:00
args = Arguments(arg_list).get_parsed_arg()
configuration = Configuration(args)
config = {}
with warnings.catch_warnings(record=True) as w:
2020-03-24 19:10:15 +00:00
warnings.simplefilter("always")
# No warnings ...
configuration._args_to_config(config, argname="strategy_path", logstring="DeadBeef")
assert len(w) == 0
2019-08-11 18:17:39 +00:00
assert log_has("DeadBeef", caplog)
assert config["strategy_path"] == "TestTest"
configuration = Configuration(args)
config = {}
with warnings.catch_warnings(record=True) as w:
2020-03-24 19:10:15 +00:00
warnings.simplefilter("always")
# Deprecation warnings!
configuration._args_to_config(
config, argname="strategy_path", logstring="DeadBeef", deprecated_msg="Going away soon!"
)
assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning)
assert "DEPRECATED: Going away soon!" in str(w[-1].message)
2019-08-11 18:17:39 +00:00
assert log_has("DeadBeef", caplog)
assert config["strategy_path"] == "TestTest"
def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None:
default_conf["max_open_trades"] = 0
patched_configuration_load_config_file(mocker, default_conf)
args = Arguments(["trade"]).get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf["max_open_trades"] == 0
assert "internals" in validated_conf
def test_load_config_combine_dicts(default_conf, mocker, caplog) -> None:
conf1 = deepcopy(default_conf)
conf2 = deepcopy(default_conf)
del conf1["exchange"]["key"]
del conf1["exchange"]["secret"]
del conf2["exchange"]["name"]
conf2["exchange"]["pair_whitelist"] += ["NANO/BTC"]
config_files = [conf1, conf2]
configsmock = MagicMock(side_effect=config_files)
mocker.patch("freqtrade.configuration.load_config.load_config_file", configsmock)
arg_list = [
"trade",
"-c",
"test_conf.json",
"--config",
"test2_conf.json",
]
2019-09-04 14:38:33 +00:00
args = Arguments(arg_list).get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration.load_config()
exchange_conf = default_conf["exchange"]
assert validated_conf["exchange"]["name"] == exchange_conf["name"]
assert validated_conf["exchange"]["key"] == exchange_conf["key"]
assert validated_conf["exchange"]["secret"] == exchange_conf["secret"]
assert validated_conf["exchange"]["pair_whitelist"] != conf1["exchange"]["pair_whitelist"]
assert validated_conf["exchange"]["pair_whitelist"] == conf2["exchange"]["pair_whitelist"]
assert "internals" in validated_conf
2019-08-10 18:02:11 +00:00
def test_from_config(default_conf, mocker, caplog) -> None:
conf1 = deepcopy(default_conf)
conf2 = deepcopy(default_conf)
del conf1["exchange"]["key"]
del conf1["exchange"]["secret"]
del conf2["exchange"]["name"]
conf2["exchange"]["pair_whitelist"] += ["NANO/BTC"]
conf2["fiat_display_currency"] = "EUR"
2019-08-10 18:02:11 +00:00
config_files = [conf1, conf2]
mocker.patch("freqtrade.configuration.configuration.create_datadir", lambda c, x: x)
2019-08-10 18:02:11 +00:00
configsmock = MagicMock(side_effect=config_files)
mocker.patch("freqtrade.configuration.load_config.load_config_file", configsmock)
2019-08-10 18:02:11 +00:00
validated_conf = Configuration.from_files(["test_conf.json", "test2_conf.json"])
2019-08-10 18:02:11 +00:00
exchange_conf = default_conf["exchange"]
assert validated_conf["exchange"]["name"] == exchange_conf["name"]
assert validated_conf["exchange"]["key"] == exchange_conf["key"]
assert validated_conf["exchange"]["secret"] == exchange_conf["secret"]
assert validated_conf["exchange"]["pair_whitelist"] != conf1["exchange"]["pair_whitelist"]
assert validated_conf["exchange"]["pair_whitelist"] == conf2["exchange"]["pair_whitelist"]
assert validated_conf["fiat_display_currency"] == "EUR"
assert "internals" in validated_conf
assert isinstance(validated_conf["user_data_dir"], Path)
2022-04-08 14:04:54 +00:00
def test_from_recursive_files(testdatadir) -> None:
files = testdatadir / "testconfigs/testconfig.json"
conf = Configuration.from_files([files])
assert conf
# Exchange comes from "the first config"
assert conf["exchange"]
2022-04-08 14:04:54 +00:00
# Pricing comes from the 2nd config
assert conf["entry_pricing"]
assert conf["entry_pricing"]["price_side"] == "same"
assert conf["exit_pricing"]
# The other key comes from pricing2, which is imported by pricing.json.
# pricing.json is a level higher, therefore wins.
assert conf["exit_pricing"]["price_side"] == "same"
2022-04-08 14:04:54 +00:00
assert len(conf["config_files"]) == 4
assert "testconfig.json" in conf["config_files"][0]
assert "test_pricing_conf.json" in conf["config_files"][1]
assert "test_base_config.json" in conf["config_files"][2]
assert "test_pricing2_conf.json" in conf["config_files"][3]
2022-04-08 15:26:51 +00:00
2022-04-08 14:04:54 +00:00
files = testdatadir / "testconfigs/recursive.json"
with pytest.raises(OperationalException, match="Config loop detected."):
load_from_files([files])
def test_print_config(default_conf, mocker, caplog) -> None:
conf1 = deepcopy(default_conf)
# Delete non-json elements from default_conf
del conf1["user_data_dir"]
conf1["datadir"] = str(conf1["datadir"])
config_files = [conf1]
configsmock = MagicMock(side_effect=config_files)
mocker.patch("freqtrade.configuration.configuration.create_datadir", lambda c, x: x)
mocker.patch("freqtrade.configuration.configuration.load_from_files", configsmock)
validated_conf = Configuration.from_files(["test_conf.json"])
assert isinstance(validated_conf["user_data_dir"], Path)
assert "user_data_dir" in validated_conf
assert "original_config" in validated_conf
assert isinstance(json.dumps(validated_conf["original_config"]), str)
def test_load_config_max_open_trades_minus_one(default_conf, mocker, caplog) -> None:
default_conf["max_open_trades"] = -1
patched_configuration_load_config_file(mocker, default_conf)
args = Arguments(["trade"]).get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf["max_open_trades"] > 999999999
assert validated_conf["max_open_trades"] == float("inf")
2018-12-25 13:35:48 +00:00
assert "runmode" in validated_conf
assert validated_conf["runmode"] == RunMode.DRY_RUN
2018-06-08 00:01:38 +00:00
def test_load_config_file_exception(mocker) -> None:
mocker.patch(
"freqtrade.configuration.configuration.Path.open",
MagicMock(side_effect=FileNotFoundError("File not found")),
)
2018-06-08 00:01:38 +00:00
with pytest.raises(OperationalException, match=r'.*Config file "somefile" not found!*'):
load_config_file("somefile")
def test_load_config(default_conf, mocker) -> None:
del default_conf["strategy_path"]
patched_configuration_load_config_file(mocker, default_conf)
args = Arguments(["trade"]).get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf.get("strategy_path") is None
assert "edge" not in validated_conf
def test_load_config_with_params(default_conf, mocker) -> None:
patched_configuration_load_config_file(mocker, default_conf)
2019-07-11 21:39:42 +00:00
2018-05-31 19:04:10 +00:00
arglist = [
"trade",
"--strategy",
"TestStrategy",
"--strategy-path",
"/some/path",
"--db-url",
"sqlite:///someurl",
]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf.get("strategy") == "TestStrategy"
assert validated_conf.get("strategy_path") == "/some/path"
assert validated_conf.get("db_url") == "sqlite:///someurl"
2018-03-27 16:29:51 +00:00
# Test conf provided db_url prod
conf = default_conf.copy()
conf["dry_run"] = False
conf["db_url"] = "sqlite:///path/to/db.sqlite"
patched_configuration_load_config_file(mocker, conf)
arglist = ["trade", "--strategy", "TestStrategy", "--strategy-path", "/some/path"]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf.get("db_url") == "sqlite:///path/to/db.sqlite"
# Test conf provided db_url dry_run
conf = default_conf.copy()
conf["dry_run"] = True
conf["db_url"] = "sqlite:///path/to/db.sqlite"
patched_configuration_load_config_file(mocker, conf)
arglist = ["trade", "--strategy", "TestStrategy", "--strategy-path", "/some/path"]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf.get("db_url") == "sqlite:///path/to/db.sqlite"
# Test args provided db_url prod
conf = default_conf.copy()
conf["dry_run"] = False
del conf["db_url"]
patched_configuration_load_config_file(mocker, conf)
arglist = ["trade", "--strategy", "TestStrategy", "--strategy-path", "/some/path"]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf.get("db_url") == DEFAULT_DB_PROD_URL
2018-12-25 13:35:48 +00:00
assert "runmode" in validated_conf
assert validated_conf["runmode"] == RunMode.LIVE
# Test args provided db_url dry_run
conf = default_conf.copy()
conf["dry_run"] = True
conf["db_url"] = DEFAULT_DB_PROD_URL
patched_configuration_load_config_file(mocker, conf)
arglist = ["trade", "--strategy", "TestStrategy", "--strategy-path", "/some/path"]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf.get("db_url") == DEFAULT_DB_DRYRUN_URL
2018-03-27 16:29:51 +00:00
@pytest.mark.parametrize(
"config_value,expected,arglist",
[
(True, True, ["trade", "--dry-run"]), # Leave config untouched
(False, True, ["trade", "--dry-run"]), # Override config untouched
(False, False, ["trade"]), # Leave config untouched
(True, True, ["trade"]), # Leave config untouched
],
)
2019-10-15 04:51:03 +00:00
def test_load_dry_run(default_conf, mocker, config_value, expected, arglist) -> None:
default_conf["dry_run"] = config_value
2019-10-15 04:51:03 +00:00
patched_configuration_load_config_file(mocker, default_conf)
configuration = Configuration(Arguments(arglist).get_parsed_arg())
validated_conf = configuration.load_config()
assert validated_conf["dry_run"] is expected
assert validated_conf["runmode"] == (RunMode.DRY_RUN if expected else RunMode.LIVE)
2019-10-15 04:51:03 +00:00
2024-06-08 07:40:32 +00:00
def test_load_custom_strategy(default_conf, mocker, tmp_path) -> None:
default_conf.update(
{
"strategy": "CustomStrategy",
2024-06-08 07:40:32 +00:00
"strategy_path": f"{tmp_path}/strategies",
}
)
patched_configuration_load_config_file(mocker, default_conf)
2018-03-27 16:29:51 +00:00
args = Arguments(["trade"]).get_parsed_arg()
2018-03-27 16:29:51 +00:00
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf.get("strategy") == "CustomStrategy"
2024-06-08 07:40:32 +00:00
assert validated_conf.get("strategy_path") == f"{tmp_path}/strategies"
def test_show_info(default_conf, mocker, caplog) -> None:
patched_configuration_load_config_file(mocker, default_conf)
2019-07-11 21:39:42 +00:00
2018-05-31 19:04:10 +00:00
arglist = [
"trade",
"--strategy",
"TestStrategy",
"--db-url",
"sqlite:///tmp/testdb",
]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
configuration = Configuration(args)
configuration.get_config()
2019-08-11 18:17:39 +00:00
assert log_has('Using DB: "sqlite:///tmp/testdb"', caplog)
assert log_has("Dry run is enabled", caplog)
def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None:
patched_configuration_load_config_file(mocker, default_conf)
2019-07-11 21:39:42 +00:00
2018-05-31 19:04:10 +00:00
arglist = [
"backtesting",
"--config",
"config.json",
"--strategy",
CURRENT_TEST_STRATEGY,
]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
configuration = Configuration(args)
config = configuration.get_config()
assert "max_open_trades" in config
assert "stake_currency" in config
assert "stake_amount" in config
assert "exchange" in config
assert "pair_whitelist" in config["exchange"]
assert "datadir" in config
assert "user_data_dir" in config
assert log_has("Using data directory: {} ...".format(config["datadir"]), caplog)
assert "timeframe" in config
assert not log_has("Parameter -i/--timeframe detected ...", caplog)
assert "position_stacking" not in config
assert not log_has("Parameter --enable-position-stacking detected ...", caplog)
assert "timerange" not in config
2024-06-08 07:40:32 +00:00
def test_setup_configuration_with_arguments(mocker, default_conf, caplog, tmp_path) -> None:
patched_configuration_load_config_file(mocker, default_conf)
mocker.patch("freqtrade.configuration.configuration.create_datadir", lambda c, x: x)
2019-07-11 22:41:09 +00:00
mocker.patch(
"freqtrade.configuration.configuration.create_userdata_dir",
lambda x, *args, **kwargs: Path(x),
2019-07-21 12:32:29 +00:00
)
2018-05-31 19:04:10 +00:00
arglist = [
"backtesting",
"--config",
"config.json",
"--strategy",
CURRENT_TEST_STRATEGY,
"--datadir",
"/foo/bar",
"--userdir",
2024-06-08 07:40:32 +00:00
f"{tmp_path}/freqtrade",
"--timeframe",
"1m",
"--enable-position-stacking",
"--disable-max-market-positions",
"--timerange",
":100",
"--export",
"trades",
"--stake-amount",
"unlimited",
]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
configuration = Configuration(args)
config = configuration.get_config()
assert "max_open_trades" in config
assert "stake_currency" in config
assert "stake_amount" in config
assert "exchange" in config
assert "pair_whitelist" in config["exchange"]
assert "datadir" in config
assert log_has("Using data directory: {} ...".format("/foo/bar"), caplog)
2024-06-08 15:41:05 +00:00
assert log_has(f"Using user-data directory: {tmp_path / 'freqtrade'} ...", caplog)
assert "user_data_dir" in config
2019-07-21 13:56:32 +00:00
assert "timeframe" in config
assert log_has("Parameter -i/--timeframe detected ... Using timeframe: 1m ...", caplog)
assert "position_stacking" in config
assert log_has("Parameter --enable-position-stacking detected ...", caplog)
2018-07-17 19:05:03 +00:00
assert "use_max_market_positions" in config
assert log_has("Parameter --disable-max-market-positions detected ...", caplog)
assert log_has("max_open_trades set to unlimited ...", caplog)
assert "timerange" in config
assert log_has("Parameter --timerange detected: {} ...".format(config["timerange"]), caplog)
assert "export" in config
assert log_has("Parameter --export detected: {} ...".format(config["export"]), caplog)
assert "stake_amount" in config
assert config["stake_amount"] == "unlimited"
2018-07-28 04:40:39 +00:00
def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> None:
"""
Test setup_configuration() function
"""
patched_configuration_load_config_file(mocker, default_conf)
2018-07-28 04:40:39 +00:00
arglist = [
"backtesting",
"--config",
"config.json",
"--timeframe",
"1m",
"--export",
"trades",
"--strategy-list",
CURRENT_TEST_STRATEGY,
"TestStrategy",
2018-07-28 04:40:39 +00:00
]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
2018-07-28 04:40:39 +00:00
2018-12-25 13:35:48 +00:00
configuration = Configuration(args, RunMode.BACKTEST)
2018-07-28 04:40:39 +00:00
config = configuration.get_config()
assert config["runmode"] == RunMode.BACKTEST
assert "max_open_trades" in config
assert "stake_currency" in config
assert "stake_amount" in config
assert "exchange" in config
assert "pair_whitelist" in config["exchange"]
assert "datadir" in config
assert log_has("Using data directory: {} ...".format(config["datadir"]), caplog)
assert "timeframe" in config
assert log_has("Parameter -i/--timeframe detected ... Using timeframe: 1m ...", caplog)
2018-07-28 04:40:39 +00:00
assert "strategy_list" in config
assert log_has("Using strategy list of 2 strategies", caplog)
2018-07-28 04:40:39 +00:00
assert "position_stacking" not in config
2018-07-28 04:40:39 +00:00
assert "use_max_market_positions" not in config
2018-07-28 04:40:39 +00:00
assert "timerange" not in config
2018-07-28 04:40:39 +00:00
assert "export" in config
assert log_has("Parameter --export detected: {} ...".format(config["export"]), caplog)
2018-07-28 04:40:39 +00:00
def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None:
patched_configuration_load_config_file(mocker, default_conf)
2019-07-11 21:39:42 +00:00
2018-05-31 19:04:10 +00:00
arglist = [
"hyperopt",
"--epochs",
"10",
"--spaces",
"all",
]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
2018-12-25 13:35:48 +00:00
configuration = Configuration(args, RunMode.HYPEROPT)
config = configuration.get_config()
assert "epochs" in config
assert int(config["epochs"]) == 10
assert log_has(
"Parameter --epochs detected ... Will run Hyperopt with for 10 epochs ...", caplog
)
assert "spaces" in config
assert config["spaces"] == ["all"]
assert log_has("Parameter -s/--spaces detected: ['all']", caplog)
2018-12-25 13:35:48 +00:00
assert "runmode" in config
assert config["runmode"] == RunMode.HYPEROPT
2018-03-30 20:14:35 +00:00
2018-07-19 19:12:27 +00:00
def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None:
patched_configuration_load_config_file(mocker, default_conf)
2019-07-11 21:39:42 +00:00
2018-07-19 19:12:27 +00:00
# Prevent setting loggers
mocker.patch("freqtrade.loggers.set_loggers", MagicMock)
arglist = ["trade", "-vvv"]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
2018-07-19 19:12:27 +00:00
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf.get("verbosity") == 3
assert log_has("Verbosity set to 3", caplog)
2018-07-19 19:12:27 +00:00
2023-11-05 15:25:23 +00:00
def test_set_logfile(default_conf, mocker, tmp_path):
patched_configuration_load_config_file(mocker, default_conf)
2023-11-05 15:25:23 +00:00
f = tmp_path / "test_file.log"
assert not f.is_file()
2019-03-29 19:16:52 +00:00
arglist = [
"trade",
"--logfile",
str(f),
2019-03-29 19:16:52 +00:00
]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
2019-03-29 19:16:52 +00:00
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf["logfile"] == str(f)
2019-03-29 19:16:52 +00:00
assert f.is_file()
2020-08-15 06:47:09 +00:00
try:
f.unlink()
except Exception:
pass
2019-03-29 19:16:52 +00:00
def test_load_config_warn_forcebuy(default_conf, mocker, caplog) -> None:
default_conf["force_entry_enable"] = True
patched_configuration_load_config_file(mocker, default_conf)
args = Arguments(["trade"]).get_parsed_arg()
configuration = Configuration(args)
validated_conf = configuration.load_config()
assert validated_conf.get("force_entry_enable")
assert log_has("`force_entry_enable` RPC message enabled.", caplog)
def test_validate_default_conf(default_conf) -> None:
# Validate via our validator - we allow setting defaults!
validate_config_schema(default_conf)
2019-01-01 13:07:40 +00:00
@pytest.mark.parametrize("fiat", ["EUR", "USD", "", None])
def test_validate_fiat_currency_options(default_conf, fiat) -> None:
# Validate via our validator - we allow setting defaults!
if fiat is not None:
default_conf["fiat_display_currency"] = fiat
else:
del default_conf["fiat_display_currency"]
validate_config_schema(default_conf)
def test_validate_max_open_trades(default_conf):
default_conf["max_open_trades"] = float("inf")
default_conf["stake_amount"] = "unlimited"
with pytest.raises(
OperationalException,
2024-05-12 15:51:21 +00:00
match="`max_open_trades` and `stake_amount` cannot both be unlimited.",
):
validate_config_consistency(default_conf)
def test_validate_price_side(default_conf):
default_conf["order_types"] = {
2022-03-08 05:59:14 +00:00
"entry": "limit",
"exit": "limit",
"stoploss": "limit",
"stoploss_on_exchange": False,
}
# Default should pass
validate_config_consistency(default_conf)
conf = deepcopy(default_conf)
conf["order_types"]["entry"] = "market"
with pytest.raises(
OperationalException,
match='Market entry orders require entry_pricing.price_side = "other".',
):
validate_config_consistency(conf)
conf = deepcopy(default_conf)
conf["order_types"]["exit"] = "market"
with pytest.raises(
OperationalException, match='Market exit orders require exit_pricing.price_side = "other".'
):
validate_config_consistency(conf)
# Validate inversed case
conf = deepcopy(default_conf)
conf["order_types"]["exit"] = "market"
conf["order_types"]["entry"] = "market"
conf["exit_pricing"]["price_side"] = "bid"
conf["entry_pricing"]["price_side"] = "ask"
validate_config_consistency(conf)
2019-03-14 08:26:31 +00:00
def test_validate_tsl(default_conf):
default_conf["stoploss"] = 0.0
with pytest.raises(
OperationalException,
match="The config stoploss needs to be different "
"from 0 to avoid problems with sell orders.",
):
2019-08-22 17:49:30 +00:00
validate_config_consistency(default_conf)
default_conf["stoploss"] = -0.10
2019-08-22 17:49:30 +00:00
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"] = True
with pytest.raises(
OperationalException,
match=r"The config trailing_only_offset_is_reached needs "
"trailing_stop_positive_offset to be more than 0 in your config.",
):
validate_config_consistency(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 needs "
"to be greater than trailing_stop_positive in your config.",
):
validate_config_consistency(default_conf)
2019-03-16 09:38:25 +00:00
default_conf["trailing_stop_positive"] = 0.01
default_conf["trailing_stop_positive_offset"] = 0.015
validate_config_consistency(default_conf)
2019-04-11 15:07:51 +00:00
2019-08-22 17:49:30 +00:00
# 0 trailing stop positive - results in "Order would trigger immediately"
default_conf["trailing_stop_positive"] = 0
default_conf["trailing_stop_positive_offset"] = 0.02
default_conf["trailing_only_offset_is_reached"] = False
with pytest.raises(
OperationalException,
match="The config trailing_stop_positive needs to be different from 0 "
"to avoid problems with sell orders",
):
2019-08-22 17:49:30 +00:00
validate_config_consistency(default_conf)
2019-04-11 15:07:51 +00:00
def test_validate_edge2(edge_conf):
edge_conf.update(
{
"use_exit_signal": True,
}
)
# Passes test
validate_config_consistency(edge_conf)
edge_conf.update(
{
"use_exit_signal": False,
}
)
with pytest.raises(
OperationalException,
2024-05-12 15:51:21 +00:00
match="Edge requires `use_exit_signal` to be True, otherwise no sells will happen.",
):
validate_config_consistency(edge_conf)
def test_validate_whitelist(default_conf):
default_conf["runmode"] = RunMode.DRY_RUN
# Test regular case - has whitelist and uses StaticPairlist
validate_config_consistency(default_conf)
conf = deepcopy(default_conf)
del conf["exchange"]["pair_whitelist"]
# Test error case
with pytest.raises(
OperationalException, match="StaticPairList requires pair_whitelist to be set."
):
validate_config_consistency(conf)
conf = deepcopy(default_conf)
conf.update(
{
"pairlists": [
{
"method": "VolumePairList",
}
]
}
)
# Dynamic whitelist should not care about pair_whitelist
validate_config_consistency(conf)
del conf["exchange"]["pair_whitelist"]
validate_config_consistency(conf)
2020-12-07 09:45:35 +00:00
@pytest.mark.parametrize(
"protconf,expected",
[
([], None),
([{"method": "StoplossGuard", "lookback_period": 2000, "stop_duration_candles": 10}], None),
([{"method": "StoplossGuard", "lookback_period_candles": 20, "stop_duration": 10}], None),
(
[
{
"method": "StoplossGuard",
"lookback_period_candles": 20,
"lookback_period": 2000,
"stop_duration": 10,
}
],
r"Protections must specify either `lookback_period`.*",
),
(
[
{
"method": "StoplossGuard",
"lookback_period": 20,
"stop_duration": 10,
"stop_duration_candles": 10,
}
],
r"Protections must specify either `stop_duration`.*",
),
],
)
def test_validate_protections(default_conf, protconf, expected):
conf = deepcopy(default_conf)
conf["protections"] = protconf
if expected:
with pytest.raises(OperationalException, match=expected):
validate_config_consistency(conf)
else:
validate_config_consistency(conf)
2021-06-25 18:51:45 +00:00
def test_validate_ask_orderbook(default_conf, caplog) -> None:
conf = deepcopy(default_conf)
conf["exit_pricing"]["use_order_book"] = True
conf["exit_pricing"]["order_book_min"] = 2
conf["exit_pricing"]["order_book_max"] = 2
2021-06-25 18:51:45 +00:00
validate_config_consistency(conf)
assert log_has_re(r"DEPRECATED: Please use `order_book_top` instead of.*", caplog)
assert conf["exit_pricing"]["order_book_top"] == 2
2021-06-25 18:51:45 +00:00
conf["exit_pricing"]["order_book_max"] = 5
2021-06-25 18:51:45 +00:00
with pytest.raises(
OperationalException, match=r"Using order_book_max != order_book_min in exit_pricing.*"
):
2021-06-25 18:51:45 +00:00
validate_config_consistency(conf)
def test_validate_time_in_force(default_conf, caplog) -> None:
conf = deepcopy(default_conf)
conf["order_time_in_force"] = {
"buy": "gtc",
"sell": "GTC",
}
validate_config_consistency(conf)
assert log_has_re(r"DEPRECATED: Using 'buy' and 'sell' for time_in_force is.*", caplog)
assert conf["order_time_in_force"]["entry"] == "gtc"
assert conf["order_time_in_force"]["exit"] == "GTC"
conf = deepcopy(default_conf)
conf["order_time_in_force"] = {
"buy": "GTC",
"sell": "GTC",
}
conf["trading_mode"] = "futures"
with pytest.raises(
OperationalException,
match=r"Please migrate your time_in_force settings .* 'entry' and 'exit'\.",
):
validate_config_consistency(conf)
def test__validate_order_types(default_conf, caplog) -> None:
2022-03-08 06:08:10 +00:00
conf = deepcopy(default_conf)
conf["order_types"] = {
"buy": "limit",
"sell": "market",
"forcesell": "market",
"forcebuy": "limit",
"stoploss": "market",
"stoploss_on_exchange": False,
2022-03-08 06:08:10 +00:00
}
validate_config_consistency(conf)
assert log_has_re(r"DEPRECATED: Using 'buy' and 'sell' for order_types is.*", caplog)
assert conf["order_types"]["entry"] == "limit"
assert conf["order_types"]["exit"] == "market"
assert conf["order_types"]["force_entry"] == "limit"
assert "buy" not in conf["order_types"]
assert "sell" not in conf["order_types"]
assert "forcebuy" not in conf["order_types"]
assert "forcesell" not in conf["order_types"]
2022-03-08 06:08:10 +00:00
conf = deepcopy(default_conf)
conf["order_types"] = {
"buy": "limit",
"sell": "market",
"forcesell": "market",
"forcebuy": "limit",
"stoploss": "market",
"stoploss_on_exchange": False,
2022-03-08 06:08:10 +00:00
}
conf["trading_mode"] = "futures"
with pytest.raises(
OperationalException,
match=r"Please migrate your order_types settings to use the new wording\.",
):
2022-03-08 06:08:10 +00:00
validate_config_consistency(conf)
def test__validate_unfilledtimeout(default_conf, caplog) -> None:
conf = deepcopy(default_conf)
conf["unfilledtimeout"] = {
"buy": 30,
"sell": 35,
}
validate_config_consistency(conf)
assert log_has_re(r"DEPRECATED: Using 'buy' and 'sell' for unfilledtimeout is.*", caplog)
assert conf["unfilledtimeout"]["entry"] == 30
assert conf["unfilledtimeout"]["exit"] == 35
assert "buy" not in conf["unfilledtimeout"]
assert "sell" not in conf["unfilledtimeout"]
conf = deepcopy(default_conf)
conf["unfilledtimeout"] = {
"buy": 30,
"sell": 35,
}
conf["trading_mode"] = "futures"
with pytest.raises(
OperationalException,
match=r"Please migrate your unfilledtimeout settings to use the new wording\.",
):
validate_config_consistency(conf)
def test__validate_pricing_rules(default_conf, caplog) -> None:
def_conf = deepcopy(default_conf)
del def_conf["entry_pricing"]
del def_conf["exit_pricing"]
def_conf["ask_strategy"] = {
"price_side": "ask",
"use_order_book": True,
"bid_last_balance": 0.5,
}
def_conf["bid_strategy"] = {
"price_side": "bid",
"use_order_book": False,
"ask_last_balance": 0.7,
}
conf = deepcopy(def_conf)
validate_config_consistency(conf)
assert log_has_re(r"DEPRECATED: Using 'ask_strategy' and 'bid_strategy' is.*", caplog)
assert conf["exit_pricing"]["price_side"] == "ask"
assert conf["exit_pricing"]["use_order_book"] is True
assert conf["exit_pricing"]["price_last_balance"] == 0.5
assert conf["entry_pricing"]["price_side"] == "bid"
assert conf["entry_pricing"]["use_order_book"] is False
assert conf["entry_pricing"]["price_last_balance"] == 0.7
assert "ask_strategy" not in conf
assert "bid_strategy" not in conf
conf = deepcopy(def_conf)
conf["trading_mode"] = "futures"
with pytest.raises(
OperationalException, match=r"Please migrate your pricing settings to use the new wording\."
):
validate_config_consistency(conf)
def test__validate_freqai_include_timeframes(default_conf, caplog) -> None:
conf = deepcopy(default_conf)
conf.update(
{
"freqai": {
"enabled": True,
"feature_parameters": {
"include_timeframes": ["1m", "5m"],
"include_corr_pairlist": [],
},
"data_split_parameters": {},
"model_training_parameters": {},
}
}
)
with pytest.raises(OperationalException, match=r"Main timeframe of .*"):
validate_config_consistency(conf)
# Validation pass
conf.update({"timeframe": "1m"})
validate_config_consistency(conf)
# Ensure base timeframe is in include_timeframes
conf["freqai"]["feature_parameters"]["include_timeframes"] = ["5m", "15m"]
validate_config_consistency(conf)
assert conf["freqai"]["feature_parameters"]["include_timeframes"] == ["1m", "5m", "15m"]
conf.update({"analyze_per_epoch": True})
with pytest.raises(
OperationalException,
match=r"Using analyze-per-epoch .* not supported with a FreqAI strategy.",
):
validate_config_consistency(conf)
2022-09-24 14:38:56 +00:00
def test__validate_consumers(default_conf, caplog) -> None:
conf = deepcopy(default_conf)
conf.update({"external_message_consumer": {"enabled": True, "producers": []}})
with pytest.raises(
OperationalException, match="You must specify at least 1 Producer to connect to."
):
2022-09-24 14:38:56 +00:00
validate_config_consistency(conf)
conf = deepcopy(default_conf)
conf.update(
{
"external_message_consumer": {
"enabled": True,
"producers": [
{
"name": "default",
"host": "127.0.0.1",
"port": 8081,
"ws_token": "secret_ws_t0ken.",
},
{
"name": "default",
"host": "127.0.0.1",
"port": 8080,
"ws_token": "secret_ws_t0ken.",
},
],
}
}
)
with pytest.raises(
OperationalException, match="Producer names must be unique. Duplicate: default"
):
2022-09-24 14:38:56 +00:00
validate_config_consistency(conf)
conf = deepcopy(default_conf)
conf.update(
{
"process_only_new_candles": True,
"external_message_consumer": {
"enabled": True,
"producers": [
{
"name": "default",
"host": "127.0.0.1",
"port": 8081,
"ws_token": "secret_ws_t0ken.",
}
],
},
}
)
2022-09-24 14:38:56 +00:00
validate_config_consistency(conf)
assert log_has_re("To receive best performance with external data.*", caplog)
def test_load_config_test_comments() -> None:
"""
Load config with comments
"""
config_file = Path(__file__).parents[0] / "config_test_comments.json"
conf = load_config_file(str(config_file))
assert conf
2019-04-11 15:07:51 +00:00
def test_load_config_default_exchange(all_conf) -> None:
2019-04-11 19:22:33 +00:00
"""
config['exchange'] subtree has required options in it
so it cannot be omitted in the config
"""
del all_conf["exchange"]
2019-04-11 15:07:51 +00:00
assert "exchange" not in all_conf
2019-04-11 15:07:51 +00:00
with pytest.raises(ValidationError, match=r"'exchange' is a required property"):
validate_config_schema(all_conf)
2019-04-11 15:07:51 +00:00
def test_load_config_default_exchange_name(all_conf) -> None:
2019-04-11 19:22:33 +00:00
"""
config['exchange']['name'] option is required
so it cannot be omitted in the config
"""
del all_conf["exchange"]["name"]
2019-04-11 15:07:51 +00:00
assert "name" not in all_conf["exchange"]
2019-04-11 15:07:51 +00:00
with pytest.raises(ValidationError, match=r"'name' is a required property"):
validate_config_schema(all_conf)
2019-04-11 15:07:51 +00:00
def test_load_config_stoploss_exchange_limit_ratio(all_conf) -> None:
all_conf["order_types"]["stoploss_on_exchange_limit_ratio"] = 1.15
with pytest.raises(ValidationError, match=r"1.15 is greater than the maximum"):
validate_config_schema(all_conf)
@pytest.mark.parametrize(
"keys",
[
("exchange", "key", ""),
("exchange", "secret", ""),
("exchange", "password", ""),
],
)
2019-04-24 07:48:25 +00:00
def test_load_config_default_subkeys(all_conf, keys) -> None:
2019-04-11 19:22:33 +00:00
"""
2019-04-24 07:48:25 +00:00
Test for parameters with default values in sub-paths
so they can be omitted in the config and the default value
2019-04-24 07:55:53 +00:00
should is added to the config.
2019-04-11 19:22:33 +00:00
"""
2019-04-24 07:48:25 +00:00
# Get first level key
key = keys[0]
# get second level key
subkey = keys[1]
2019-04-11 15:07:51 +00:00
2019-04-24 07:48:25 +00:00
del all_conf[key][subkey]
2019-04-11 15:07:51 +00:00
2019-04-24 07:48:25 +00:00
assert subkey not in all_conf[key]
2019-04-11 15:07:51 +00:00
validate_config_schema(all_conf)
2019-04-24 07:48:25 +00:00
assert subkey in all_conf[key]
assert all_conf[key][subkey] == keys[2]
def test_pairlist_resolving():
arglist = ["download-data", "--pairs", "ETH/BTC", "XRP/BTC", "--exchange", "binance"]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
2019-10-25 05:12:50 +00:00
configuration = Configuration(args, RunMode.OTHER)
config = configuration.get_config()
assert config["pairs"] == ["ETH/BTC", "XRP/BTC"]
assert config["exchange"]["pair_whitelist"] == ["ETH/BTC", "XRP/BTC"]
assert config["exchange"]["name"] == "binance"
def test_pairlist_resolving_with_config(mocker, default_conf):
patched_configuration_load_config_file(mocker, default_conf)
arglist = [
"download-data",
"--config",
"config.json",
]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
configuration = Configuration(args)
config = configuration.get_config()
assert config["pairs"] == default_conf["exchange"]["pair_whitelist"]
assert config["exchange"]["name"] == default_conf["exchange"]["name"]
# Override pairs
arglist = [
"download-data",
"--config",
"config.json",
"--pairs",
"ETH/BTC",
"XRP/BTC",
]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
configuration = Configuration(args)
config = configuration.get_config()
assert config["pairs"] == ["ETH/BTC", "XRP/BTC"]
assert config["exchange"]["name"] == default_conf["exchange"]["name"]
def test_pairlist_resolving_with_config_pl(mocker, default_conf):
patched_configuration_load_config_file(mocker, default_conf)
arglist = [
"download-data",
"--config",
"config.json",
"--pairs-file",
"tests/testdata/pairs.json",
]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
configuration = Configuration(args)
config = configuration.get_config()
assert len(config["pairs"]) == 23
assert "ETH/BTC" in config["pairs"]
assert "XRP/BTC" in config["pairs"]
assert config["exchange"]["name"] == default_conf["exchange"]["name"]
def test_pairlist_resolving_with_config_pl_not_exists(mocker, default_conf):
patched_configuration_load_config_file(mocker, default_conf)
arglist = [
"download-data",
"--config",
"config.json",
"--pairs-file",
"tests/testdata/pairs_doesnotexist.json",
]
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
with pytest.raises(OperationalException, match=r"No pairs file found with path.*"):
configuration = Configuration(args)
configuration.get_config()
2019-08-17 05:05:42 +00:00
2023-11-05 15:15:36 +00:00
def test_pairlist_resolving_fallback(mocker, tmp_path):
2019-08-17 05:05:42 +00:00
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
2019-08-21 04:59:07 +00:00
mocker.patch.object(Path, "open", MagicMock(return_value=MagicMock()))
mocker.patch(
"freqtrade.configuration.configuration.load_file",
MagicMock(return_value=["XRP/BTC", "ETH/BTC"]),
)
arglist = ["download-data", "--exchange", "binance"]
2019-08-17 05:05:42 +00:00
2019-09-04 14:38:33 +00:00
args = Arguments(arglist).get_parsed_arg()
# Fix flaky tests if config.json exists
args["config"] = None
2019-08-17 05:05:42 +00:00
configuration = Configuration(args, RunMode.OTHER)
2019-08-17 05:05:42 +00:00
config = configuration.get_config()
assert config["pairs"] == ["ETH/BTC", "XRP/BTC"]
assert config["exchange"]["name"] == "binance"
assert config["datadir"] == tmp_path / "user_data/data/binance"
@pytest.mark.parametrize(
"setting",
[
("webhook", "webhookbuy", "testWEbhook", "webhook", "webhookentry", "testWEbhook"),
(
"ask_strategy",
"ignore_buying_expired_candle_after",
5,
None,
"ignore_buying_expired_candle_after",
6,
),
],
)
def test_process_temporary_deprecated_settings(mocker, default_conf, setting, caplog):
2019-10-08 23:44:04 +00:00
patched_configuration_load_config_file(mocker, default_conf)
# Create sections for new and deprecated settings
# (they may not exist in the config)
default_conf[setting[0]] = {}
2019-10-08 23:44:04 +00:00
default_conf[setting[3]] = {}
# Assign deprecated setting
default_conf[setting[0]][setting[1]] = setting[2]
# Assign new setting
if setting[3]:
default_conf[setting[3]][setting[4]] = setting[5]
else:
default_conf[setting[4]] = setting[5]
2019-10-08 23:44:04 +00:00
# New and deprecated settings are conflicting ones
with pytest.raises(OperationalException, match=r"DEPRECATED"):
2019-10-08 23:44:04 +00:00
process_temporary_deprecated_settings(default_conf)
caplog.clear()
# Delete new setting
if setting[3]:
del default_conf[setting[3]][setting[4]]
else:
del default_conf[setting[4]]
2019-10-08 23:44:04 +00:00
process_temporary_deprecated_settings(default_conf)
assert log_has_re("DEPRECATED", caplog)
# The value of the new setting shall have been set to the
# value of the deprecated one
if setting[3]:
assert default_conf[setting[3]][setting[4]] == setting[2]
else:
assert default_conf[setting[4]] == setting[2]
@pytest.mark.parametrize(
"setting",
[
("experimental", "use_sell_signal", False),
("experimental", "sell_profit_only", True),
("experimental", "ignore_roi_if_buy_signal", True),
],
)
def test_process_removed_settings(mocker, default_conf, setting):
patched_configuration_load_config_file(mocker, default_conf)
# Create sections for new and deprecated settings
# (they may not exist in the config)
default_conf[setting[0]] = {}
# Assign removed setting
default_conf[setting[0]][setting[1]] = setting[2]
# New and deprecated settings are conflicting ones
with pytest.raises(OperationalException, match=r"Setting .* has been moved"):
process_temporary_deprecated_settings(default_conf)
def test_process_deprecated_setting_edge(mocker, edge_conf):
2020-01-03 09:58:31 +00:00
patched_configuration_load_config_file(mocker, edge_conf)
edge_conf.update(
{
"edge": {
"enabled": True,
"capital_available_percentage": 0.5,
}
}
)
2020-01-03 09:58:31 +00:00
with pytest.raises(
OperationalException, match=r"DEPRECATED.*Using 'edge.capital_available_percentage'*"
):
process_temporary_deprecated_settings(edge_conf)
2020-01-03 09:58:31 +00:00
def test_check_conflicting_settings(mocker, default_conf, caplog):
patched_configuration_load_config_file(mocker, default_conf)
# Create sections for new and deprecated settings
# (they may not exist in the config)
default_conf["sectionA"] = {}
default_conf["sectionB"] = {}
# Assign new setting
default_conf["sectionA"]["new_setting"] = "valA"
# Assign deprecated setting
default_conf["sectionB"]["deprecated_setting"] = "valB"
# New and deprecated settings are conflicting ones
with pytest.raises(OperationalException, match=r"DEPRECATED"):
check_conflicting_settings(
default_conf, "sectionB", "deprecated_setting", "sectionA", "new_setting"
)
caplog.clear()
# Delete new setting (deprecated exists)
del default_conf["sectionA"]["new_setting"]
check_conflicting_settings(
default_conf, "sectionB", "deprecated_setting", "sectionA", "new_setting"
)
assert not log_has_re("DEPRECATED", caplog)
assert "new_setting" not in default_conf["sectionA"]
caplog.clear()
# Assign new setting
default_conf["sectionA"]["new_setting"] = "valA"
# Delete deprecated setting
del default_conf["sectionB"]["deprecated_setting"]
check_conflicting_settings(
default_conf, "sectionB", "deprecated_setting", "sectionA", "new_setting"
)
assert not log_has_re("DEPRECATED", caplog)
assert default_conf["sectionA"]["new_setting"] == "valA"
def test_process_deprecated_setting(mocker, default_conf, caplog):
patched_configuration_load_config_file(mocker, default_conf)
# Create sections for new and deprecated settings
# (they may not exist in the config)
default_conf["sectionA"] = {}
default_conf["sectionB"] = {}
# Assign deprecated setting
default_conf["sectionB"]["deprecated_setting"] = "valB"
# Both new and deprecated settings exists
process_deprecated_setting(
default_conf, "sectionB", "deprecated_setting", "sectionA", "new_setting"
)
assert log_has_re("DEPRECATED", caplog)
# The value of the new setting shall have been set to the
# value of the deprecated one
assert default_conf["sectionA"]["new_setting"] == "valB"
# Old setting is removed
assert "deprecated_setting" not in default_conf["sectionB"]
caplog.clear()
# Delete new setting (deprecated exists)
del default_conf["sectionA"]["new_setting"]
default_conf["sectionB"]["deprecated_setting"] = "valB"
process_deprecated_setting(
default_conf, "sectionB", "deprecated_setting", "sectionA", "new_setting"
)
assert log_has_re("DEPRECATED", caplog)
# The value of the new setting shall have been set to the
# value of the deprecated one
assert default_conf["sectionA"]["new_setting"] == "valB"
caplog.clear()
# Assign new setting
default_conf["sectionA"]["new_setting"] = "valA"
# Delete deprecated setting
default_conf["sectionB"].pop("deprecated_setting", None)
process_deprecated_setting(
default_conf, "sectionB", "deprecated_setting", "sectionA", "new_setting"
)
assert not log_has_re("DEPRECATED", caplog)
assert default_conf["sectionA"]["new_setting"] == "valA"
2020-06-02 08:11:50 +00:00
caplog.clear()
# Test moving to root
default_conf["sectionB"]["deprecated_setting2"] = "DeadBeef"
process_deprecated_setting(default_conf, "sectionB", "deprecated_setting2", None, "new_setting")
assert log_has_re("DEPRECATED", caplog)
assert default_conf["new_setting"]
2020-06-02 08:11:50 +00:00
def test_process_removed_setting(mocker, default_conf, caplog):
patched_configuration_load_config_file(mocker, default_conf)
# Create sections for new and deprecated settings
# (they may not exist in the config)
default_conf["sectionA"] = {}
default_conf["sectionB"] = {}
# Assign new setting
default_conf["sectionB"]["somesetting"] = "valA"
# Only new setting exists (nothing should happen)
process_removed_setting(default_conf, "sectionA", "somesetting", "sectionB", "somesetting")
# Assign removed setting
default_conf["sectionA"]["somesetting"] = "valB"
with pytest.raises(OperationalException, match=r"Setting .* has been moved"):
process_removed_setting(default_conf, "sectionA", "somesetting", "sectionB", "somesetting")
2021-08-04 17:43:16 +00:00
def test_process_deprecated_ticker_interval(default_conf, caplog):
2020-06-02 08:11:50 +00:00
message = "DEPRECATED: Please use 'timeframe' instead of 'ticker_interval."
config = deepcopy(default_conf)
process_temporary_deprecated_settings(config)
2020-06-02 08:11:50 +00:00
assert not log_has(message, caplog)
del config["timeframe"]
config["ticker_interval"] = "15m"
with pytest.raises(
OperationalException, match=r"DEPRECATED: 'ticker_interval' detected. Please use.*"
):
process_temporary_deprecated_settings(config)
2021-07-31 15:43:10 +00:00
2021-08-04 17:43:16 +00:00
def test_process_deprecated_protections(default_conf, caplog):
message = "DEPRECATED: Setting 'protections' in the configuration is deprecated."
config = deepcopy(default_conf)
process_temporary_deprecated_settings(config)
assert not log_has(message, caplog)
config["protections"] = []
2021-08-04 17:43:16 +00:00
process_temporary_deprecated_settings(config)
assert log_has(message, caplog)
2021-07-31 15:43:10 +00:00
def test_flat_vars_to_nested_dict(caplog):
test_args = {
"FREQTRADE__EXCHANGE__SOME_SETTING": "true",
"FREQTRADE__EXCHANGE__SOME_FALSE_SETTING": "false",
"FREQTRADE__EXCHANGE__CONFIG__whatever": "sometime",
"FREQTRADE__EXIT_PRICING__PRICE_SIDE": "bid",
"FREQTRADE__EXIT_PRICING__cccc": "500",
"FREQTRADE__STAKE_AMOUNT": "200.05",
"FREQTRADE__TELEGRAM__CHAT_ID": "2151",
"NOT_RELEVANT": "200.0", # Will be ignored
2021-07-31 15:43:10 +00:00
}
expected = {
"stake_amount": 200.05,
"exit_pricing": {
"price_side": "bid",
"cccc": 500,
2021-07-31 15:43:10 +00:00
},
"exchange": {
"config": {
"whatever": "sometime",
2021-07-31 15:43:10 +00:00
},
"some_setting": True,
"some_false_setting": False,
},
"telegram": {"chat_id": "2151"},
2021-07-31 15:43:10 +00:00
}
res = _flat_vars_to_nested_dict(test_args, ENV_VAR_PREFIX)
2021-07-31 15:43:10 +00:00
assert res == expected
assert log_has("Loading variable 'FREQTRADE__EXCHANGE__SOME_SETTING'", caplog)
assert not log_has("Loading variable 'NOT_RELEVANT'", caplog)
def test_setup_hyperopt_freqai(mocker, default_conf) -> None:
patched_configuration_load_config_file(mocker, default_conf)
mocker.patch("freqtrade.configuration.configuration.create_datadir", lambda c, x: x)
mocker.patch(
"freqtrade.configuration.configuration.create_userdata_dir",
lambda x, *args, **kwargs: Path(x),
)
arglist = [
"hyperopt",
"--config",
"config.json",
"--strategy",
CURRENT_TEST_STRATEGY,
"--timerange",
"20220801-20220805",
"--freqaimodel",
"LightGBMRegressorMultiTarget",
"--analyze-per-epoch",
]
args = Arguments(arglist).get_parsed_arg()
configuration = Configuration(args)
config = configuration.get_config()
config["freqai"] = {"enabled": True}
with pytest.raises(
OperationalException, match=r".*analyze-per-epoch parameter is not supported.*"
):
validate_config_consistency(config)
def test_setup_freqai_backtesting(mocker, default_conf) -> None:
patched_configuration_load_config_file(mocker, default_conf)
mocker.patch("freqtrade.configuration.configuration.create_datadir", lambda c, x: x)
mocker.patch(
"freqtrade.configuration.configuration.create_userdata_dir",
lambda x, *args, **kwargs: Path(x),
)
arglist = [
"backtesting",
"--config",
"config.json",
"--strategy",
CURRENT_TEST_STRATEGY,
"--timerange",
"20220801-20220805",
"--freqaimodel",
"LightGBMRegressorMultiTarget",
"--freqai-backtest-live-models",
]
args = Arguments(arglist).get_parsed_arg()
configuration = Configuration(args)
config = configuration.get_config()
config["runmode"] = RunMode.BACKTEST
2022-09-28 11:48:32 +00:00
with pytest.raises(
OperationalException, match=r".*--freqai-backtest-live-models parameter is only.*"
):
validate_config_consistency(config)
conf = deepcopy(config)
conf["freqai"] = {"enabled": True}
with pytest.raises(
OperationalException, match=r".* timerange parameter is not supported with .*"
):
validate_config_consistency(conf)
2022-09-28 11:48:32 +00:00
conf["timerange"] = None
conf["freqai_backtest_live_models"] = False
2022-09-28 11:48:32 +00:00
with pytest.raises(
OperationalException, match=r".* pass --timerange if you intend to use FreqAI .*"
):
validate_config_consistency(conf)
def test_sanitize_config(default_conf_usdt):
assert default_conf_usdt["exchange"]["key"] != "REDACTED"
res = sanitize_config(default_conf_usdt)
2024-03-20 06:22:12 +00:00
# Didn't modify original dict
assert default_conf_usdt["exchange"]["key"] != "REDACTED"
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"]