diff --git a/.travis.yml b/.travis.yml index b066e7044..a45334dd6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -32,7 +32,7 @@ jobs: name: backtest - script: - cp config.json.example config.json - - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy DefaultStrategy + - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy DefaultStrategy --customhyperopt DefaultHyperOpts name: hyperopt - script: flake8 name: flake8 diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 2b66d3c25..8f7e0bbcf 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -285,8 +285,8 @@ optional arguments: --stake_amount STAKE_AMOUNT Specify stake_amount. --customhyperopt NAME - Specify hyperopt class name (default: - `DefaultHyperOpts`). + Specify hyperopt class name which will be used by the + bot. --hyperopt-path PATH Specify additional lookup path for Hyperopts and Hyperopt Loss functions. --eps, --enable-position-stacking @@ -322,9 +322,10 @@ optional arguments: generate completely different results, since the target for optimization is different. Built-in Hyperopt-loss-functions are: DefaultHyperOptLoss, - OnlyProfitHyperOptLoss, SharpeHyperOptLoss.(default: + OnlyProfitHyperOptLoss, SharpeHyperOptLoss (default: `DefaultHyperOptLoss`). + Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). --logfile FILE Log to the file specified. diff --git a/freqtrade/configuration/cli_options.py b/freqtrade/configuration/cli_options.py index 2ecd4cfc5..ee0d94023 100644 --- a/freqtrade/configuration/cli_options.py +++ b/freqtrade/configuration/cli_options.py @@ -153,9 +153,8 @@ AVAILABLE_CLI_OPTIONS = { # Hyperopt "hyperopt": Arg( '--customhyperopt', - help='Specify hyperopt class name (default: `%(default)s`).', + help='Specify hyperopt class name which will be used by the bot.', metavar='NAME', - default=constants.DEFAULT_HYPEROPT, ), "hyperopt_path": Arg( '--hyperopt-path', @@ -232,7 +231,7 @@ AVAILABLE_CLI_OPTIONS = { help='Specify the class name of the hyperopt loss function class (IHyperOptLoss). ' 'Different functions can generate completely different results, ' 'since the target for optimization is different. Built-in Hyperopt-loss-functions are: ' - 'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss.' + 'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss ' '(default: `%(default)s`).', metavar='NAME', default=constants.DEFAULT_HYPEROPT_LOSS, diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 749ae25b5..b053519b0 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -9,7 +9,6 @@ PROCESS_THROTTLE_SECS = 5 # sec DEFAULT_TICKER_INTERVAL = 5 # min HYPEROPT_EPOCH = 100 # epochs RETRY_TIMEOUT = 30 # sec -DEFAULT_HYPEROPT = 'DefaultHyperOpts' DEFAULT_HYPEROPT_LOSS = 'DefaultHyperOptLoss' DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite' DEFAULT_DB_DRYRUN_URL = 'sqlite://' diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py index e96394d69..15080cda5 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -8,7 +8,7 @@ from pathlib import Path from typing import Optional, Dict from freqtrade import OperationalException -from freqtrade.constants import DEFAULT_HYPEROPT, DEFAULT_HYPEROPT_LOSS +from freqtrade.constants import DEFAULT_HYPEROPT_LOSS from freqtrade.optimize.hyperopt_interface import IHyperOpt from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss from freqtrade.resolvers import IResolver @@ -20,7 +20,6 @@ class HyperOptResolver(IResolver): """ This class contains all the logic to load custom hyperopt class """ - __slots__ = ['hyperopt'] def __init__(self, config: Dict) -> None: @@ -28,9 +27,12 @@ class HyperOptResolver(IResolver): Load the custom class from config parameter :param config: configuration dictionary """ + if not config.get('hyperopt'): + raise OperationalException("No Hyperopt set. Please use `--customhyperopt` to specify " + "the Hyperopt class to use.") + + hyperopt_name = config['hyperopt'] - # Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt - hyperopt_name = config.get('hyperopt') or DEFAULT_HYPEROPT self.hyperopt = self._load_hyperopt(hyperopt_name, config, extra_dir=config.get('hyperopt_path')) @@ -75,27 +77,28 @@ class HyperOptLossResolver(IResolver): """ This class contains all the logic to load custom hyperopt loss class """ - __slots__ = ['hyperoptloss'] - def __init__(self, config: Dict = None) -> None: + def __init__(self, config: Dict) -> None: """ Load the custom class from config parameter - :param config: configuration dictionary or None + :param config: configuration dictionary """ - config = config or {} - # Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt - hyperopt_name = config.get('hyperopt_loss') or DEFAULT_HYPEROPT_LOSS + # Verify the hyperopt_loss is in the configuration, otherwise fallback to the + # default hyperopt loss + hyperoptloss_name = config.get('hyperopt_loss') or DEFAULT_HYPEROPT_LOSS + self.hyperoptloss = self._load_hyperoptloss( - hyperopt_name, config, extra_dir=config.get('hyperopt_path')) + hyperoptloss_name, config, extra_dir=config.get('hyperopt_path')) # Assign ticker_interval to be used in hyperopt self.hyperoptloss.__class__.ticker_interval = str(config['ticker_interval']) if not hasattr(self.hyperoptloss, 'hyperopt_loss_function'): raise OperationalException( - f"Found hyperopt {hyperopt_name} does not implement `hyperopt_loss_function`.") + f"Found HyperoptLoss class {hyperoptloss_name} does not " + "implement `hyperopt_loss_function`.") def _load_hyperoptloss( self, hyper_loss_name: str, config: Dict, diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 5ff11d5ea..e1ee649c8 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -25,7 +25,10 @@ from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, @pytest.fixture(scope='function') def hyperopt(default_conf, mocker): - default_conf.update({'spaces': ['all']}) + default_conf.update({ + 'spaces': ['all'], + 'hyperopt': 'DefaultHyperOpts', + }) patch_exchange(mocker) return Hyperopt(default_conf) @@ -70,6 +73,7 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca args = [ 'hyperopt', '--config', 'config.json', + '--customhyperopt', 'DefaultHyperOpts', ] config = setup_configuration(get_args(args), RunMode.HYPEROPT) @@ -101,6 +105,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo args = [ 'hyperopt', '--config', 'config.json', + '--customhyperopt', 'DefaultHyperOpts', '--datadir', '/foo/bar', '--ticker-interval', '1m', '--timerange', ':100', @@ -155,7 +160,8 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None: 'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver._load_hyperopt', MagicMock(return_value=hyperopts(default_conf)) ) - x = HyperOptResolver(default_conf, ).hyperopt + default_conf.update({'hyperopt': 'DefaultHyperOpts'}) + x = HyperOptResolver(default_conf).hyperopt assert not hasattr(x, 'populate_buy_trend') assert not hasattr(x, 'populate_sell_trend') assert log_has("Hyperopt class does not provide populate_sell_trend() method. " @@ -169,7 +175,15 @@ def test_hyperoptresolver_wrongname(mocker, default_conf, caplog) -> None: default_conf.update({'hyperopt': "NonExistingHyperoptClass"}) with pytest.raises(OperationalException, match=r'Impossible to load Hyperopt.*'): - HyperOptResolver(default_conf, ).hyperopt + HyperOptResolver(default_conf).hyperopt + + +def test_hyperoptresolver_noname(default_conf): + default_conf['hyperopt'] = '' + with pytest.raises(OperationalException, + match="No Hyperopt set. Please use `--customhyperopt` to specify " + "the Hyperopt class to use."): + HyperOptResolver(default_conf) def test_hyperoptlossresolver(mocker, default_conf, caplog) -> None: @@ -179,7 +193,7 @@ def test_hyperoptlossresolver(mocker, default_conf, caplog) -> None: 'freqtrade.resolvers.hyperopt_resolver.HyperOptLossResolver._load_hyperoptloss', MagicMock(return_value=hl) ) - x = HyperOptLossResolver(default_conf, ).hyperoptloss + x = HyperOptLossResolver(default_conf).hyperoptloss assert hasattr(x, "hyperopt_loss_function") @@ -187,7 +201,7 @@ def test_hyperoptlossresolver_wrongname(mocker, default_conf, caplog) -> None: default_conf.update({'hyperopt_loss': "NonExistingLossClass"}) with pytest.raises(OperationalException, match=r'Impossible to load HyperoptLoss.*'): - HyperOptLossResolver(default_conf, ).hyperopt + HyperOptLossResolver(default_conf).hyperopt def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None: @@ -200,6 +214,7 @@ def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None args = [ 'hyperopt', '--config', 'config.json', + '--customhyperopt', 'DefaultHyperOpts', '--epochs', '5' ] args = get_args(args) @@ -217,6 +232,7 @@ def test_start(mocker, default_conf, caplog) -> None: args = [ 'hyperopt', '--config', 'config.json', + '--customhyperopt', 'DefaultHyperOpts', '--epochs', '5' ] args = get_args(args) @@ -239,6 +255,7 @@ def test_start_no_data(mocker, default_conf, caplog) -> None: args = [ 'hyperopt', '--config', 'config.json', + '--customhyperopt', 'DefaultHyperOpts', '--epochs', '5' ] args = get_args(args) @@ -256,6 +273,7 @@ def test_start_filelock(mocker, default_conf, caplog) -> None: args = [ 'hyperopt', '--config', 'config.json', + '--customhyperopt', 'DefaultHyperOpts', '--epochs', '5' ] args = get_args(args) @@ -407,6 +425,7 @@ def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None: patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -510,10 +529,12 @@ def test_buy_strategy_generator(hyperopt, testdatadir) -> None: def test_generate_optimizer(mocker, default_conf) -> None: - default_conf.update({'config': 'config.json.example'}) - default_conf.update({'timerange': None}) - default_conf.update({'spaces': 'all'}) - default_conf.update({'hyperopt_min_trades': 1}) + default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', + 'timerange': None, + 'spaces': 'all', + 'hyperopt_min_trades': 1, + }) trades = [ ('POWR/BTC', 0.023117, 0.000233, 100) @@ -576,6 +597,7 @@ def test_generate_optimizer(mocker, default_conf) -> None: def test_clean_hyperopt(mocker, default_conf, caplog): patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -592,6 +614,7 @@ def test_clean_hyperopt(mocker, default_conf, caplog): def test_continue_hyperopt(mocker, default_conf, caplog): patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -621,6 +644,7 @@ def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None: patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -658,6 +682,7 @@ def test_print_json_spaces_roi_stoploss(mocker, default_conf, caplog, capsys) -> patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', 'epochs': 1, 'timerange': None, 'spaces': 'roi stoploss', @@ -696,6 +721,7 @@ def test_simplified_interface_roi_stoploss(mocker, default_conf, caplog, capsys) patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', 'epochs': 1, 'timerange': None, 'spaces': 'roi stoploss', @@ -737,6 +763,7 @@ def test_simplified_interface_all_failed(mocker, default_conf, caplog, capsys) - patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', 'epochs': 1, 'timerange': None, 'spaces': 'all', @@ -770,6 +797,7 @@ def test_simplified_interface_buy(mocker, default_conf, caplog, capsys) -> None: patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', 'epochs': 1, 'timerange': None, 'spaces': 'buy', @@ -815,6 +843,7 @@ def test_simplified_interface_sell(mocker, default_conf, caplog, capsys) -> None patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', 'epochs': 1, 'timerange': None, 'spaces': 'sell', @@ -862,6 +891,7 @@ def test_simplified_interface_failed(mocker, default_conf, caplog, capsys, metho patch_exchange(mocker) default_conf.update({'config': 'config.json.example', + 'hyperopt': 'DefaultHyperOpts', 'epochs': 1, 'timerange': None, 'spaces': space,