diff --git a/docs/advanced-hyperopt.md b/docs/advanced-hyperopt.md index 5fc674b03..dfabf2b91 100644 --- a/docs/advanced-hyperopt.md +++ b/docs/advanced-hyperopt.md @@ -27,9 +27,9 @@ class MyAwesomeHyperOpt2(MyAwesomeHyperOpt): and then quickly switch between hyperopt classes, running optimization process with hyperopt class you need in each particular case: ``` -$ freqtrade hyperopt --hyperopt MyAwesomeHyperOpt ... +$ freqtrade hyperopt --hyperopt MyAwesomeHyperOpt --strategy MyAwesomeStrategy ... or -$ freqtrade hyperopt --hyperopt MyAwesomeHyperOpt2 ... +$ freqtrade hyperopt --hyperopt MyAwesomeHyperOpt2 --strategy MyAwesomeStrategy ... ``` ## Creating and using a custom loss function diff --git a/docs/bot-usage.md b/docs/bot-usage.md index 4a4496bbc..62c515b44 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -303,7 +303,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...]] [--dmmp] [--print-all] [--no-color] [--print-json] [-j JOBS] [--random-state INT] [--min-trades INT] - [--continue] [--hyperopt-loss NAME] + [--hyperopt-loss NAME] optional arguments: -h, --help show this help message and exit @@ -349,9 +349,6 @@ optional arguments: reproducible hyperopt results. --min-trades INT Set minimal desired number of trades for evaluations in the hyperopt optimization path (default: 1). - --continue Continue hyperopt from previous runs. By default, - temporary files will be removed and hyperopt will - start from scratch. --hyperopt-loss NAME Specify the class name of the hyperopt loss function class (IHyperOptLoss). Different functions can generate completely different results, since the diff --git a/docs/faq.md b/docs/faq.md index beed89801..d8af7798c 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -140,13 +140,7 @@ Since hyperopt uses Bayesian search, running for too many epochs may not produce It's therefore recommended to run between 500-1000 epochs over and over until you hit at least 10.000 epochs in total (or are satisfied with the result). You can best judge by looking at the results - if the bot keeps discovering better strategies, it's best to keep on going. ```bash -freqtrade hyperopt -e 1000 -``` - -or if you want intermediate result to see - -```bash -for i in {1..100}; do freqtrade hyperopt -e 1000; done +freqtrade hyperopt --hyperop SampleHyperopt --strategy SampleStrategy -e 1000 ``` ### Why does it take a long time to run hyperopt? diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 3f7a27ef0..d26cbeeb2 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -102,7 +102,7 @@ There you have two different types of indicators: 1. `guards` and 2. `triggers`. 1. Guards are conditions like "never buy if ADX < 10", or never buy if current price is over EMA10. 2. Triggers are ones that actually trigger buy in specific moment, like "buy when EMA5 crosses over EMA10" or "buy when close price touches lower Bollinger band". -Hyperoptimization will, for each eval round, pick one trigger and possibly +Hyper-optimization will, for each epoch round, pick one trigger and possibly multiple guards. The constructed strategy will be something like "*buy exactly when close price touches lower Bollinger band, BUT only if ADX > 10*". @@ -229,7 +229,7 @@ Because hyperopt tries a lot of combinations to find the best parameters it will We strongly recommend to use `screen` or `tmux` to prevent any connection loss. ```bash -freqtrade hyperopt --config config.json --hyperopt -e 500 --spaces all +freqtrade hyperopt --config config.json --hyperopt --strategy -e 500 --spaces all ``` Use `` as the name of the custom hyperopt used. @@ -240,10 +240,8 @@ running at least several thousand evaluations. The `--spaces all` option determines that all possible parameters should be optimized. Possibilities are listed below. !!! Note - By default, hyperopt will erase previous results and start from scratch. Continuation can be archived by using `--continue`. - -!!! Warning - When switching parameters or changing configuration options, make sure to not use the argument `--continue` so temporary results can be removed. + Hyperopt will store hyperopt results with the timestamp of the hyperopt start time. + Reading commands (`hyperopt-list`, `hyperopt-show`) can use `--hyperopt-filename ` to read and display older hyperopt results. ### Execute Hyperopt with different historical data source @@ -253,11 +251,11 @@ uses data from directory `user_data/data`. ### Running Hyperopt with Smaller Testset -Use the `--timerange` argument to change how much of the testset you want to use. +Use the `--timerange` argument to change how much of the test-set you want to use. For example, to use one month of data, pass the following parameter to the hyperopt call: ```bash -freqtrade hyperopt --timerange 20180401-20180501 +freqtrade hyperopt --hyperopt --strategy --timerange 20180401-20180501 ``` ### Running Hyperopt using methods from a strategy @@ -318,7 +316,7 @@ The initial state for generation of these random values (random state) is contro If you have not set this value explicitly in the command line options, Hyperopt seeds the random state with some random value for you. The random state value for each Hyperopt run is shown in the log, so you can copy and paste it into the `--random-state` command line option to repeat the set of the initial random epochs used. -If you have not changed anything in the command line options, configuration, timerange, Strategy and Hyperopt classes, historical data and the Loss Function -- you should obtain same hyperoptimization results with same random state value used. +If you have not changed anything in the command line options, configuration, timerange, Strategy and Hyperopt classes, historical data and the Loss Function -- you should obtain same hyper-optimization results with same random state value used. ## Understand the Hyperopt Result @@ -371,7 +369,7 @@ By default, hyperopt prints colorized results -- epochs with positive profit are You can use the `--print-all` command line option if you would like to see all results in the hyperopt output, not only the best ones. When `--print-all` is used, current best results are also colorized by default -- they are printed in bold (bright) style. This can also be switched off with the `--no-color` command line option. !!! Note "Windows and color output" - Windows does not support color-output nativly, therefore it is automatically disabled. To have color-output for hyperopt running under windows, please consider using WSL. + Windows does not support color-output natively, therefore it is automatically disabled. To have color-output for hyperopt running under windows, please consider using WSL. ### Understand Hyperopt ROI results @@ -494,7 +492,7 @@ Override the `trailing_space()` method and define the desired range in it if you ## Show details of Hyperopt results -After you run Hyperopt for the desired amount of epochs, you can later list all results for analysis, select only best or profitable once, and show the details for any of the epochs previously evaluated. This can be done with the `hyperopt-list` and `hyperopt-show` subcommands. The usage of these subcommands is described in the [Utils](utils.md#list-hyperopt-results) chapter. +After you run Hyperopt for the desired amount of epochs, you can later list all results for analysis, select only best or profitable once, and show the details for any of the epochs previously evaluated. This can be done with the `hyperopt-list` and `hyperopt-show` sub-commands. The usage of these sub-commands is described in the [Utils](utils.md#list-hyperopt-results) chapter. ## Validate backtesting results diff --git a/docs/utils.md b/docs/utils.md index 8c7e381ff..409bcc134 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -423,7 +423,7 @@ freqtrade test-pairlist --config config.json --quote USDT BTC ## List Hyperopt results -You can list the hyperoptimization epochs the Hyperopt module evaluated previously with the `hyperopt-list` subcommand. +You can list the hyperoptimization epochs the Hyperopt module evaluated previously with the `hyperopt-list` sub-command. ``` usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH] @@ -432,10 +432,11 @@ usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH] [--max-trades INT] [--min-avg-time FLOAT] [--max-avg-time FLOAT] [--min-avg-profit FLOAT] [--max-avg-profit FLOAT] - [--min-total-profit FLOAT] [--max-total-profit FLOAT] + [--min-total-profit FLOAT] + [--max-total-profit FLOAT] [--min-objective FLOAT] [--max-objective FLOAT] [--no-color] [--print-json] [--no-details] - [--export-csv FILE] + [--hyperopt-filename PATH] [--export-csv FILE] optional arguments: -h, --help show this help message and exit @@ -443,24 +444,27 @@ optional arguments: --profitable Select only profitable epochs. --min-trades INT Select epochs with more than INT trades. --max-trades INT Select epochs with less than INT trades. - --min-avg-time FLOAT Select epochs on above average time. - --max-avg-time FLOAT Select epochs on under average time. + --min-avg-time FLOAT Select epochs above average time. + --max-avg-time FLOAT Select epochs below average time. --min-avg-profit FLOAT - Select epochs on above average profit. + Select epochs above average profit. --max-avg-profit FLOAT - Select epochs on below average profit. + Select epochs below average profit. --min-total-profit FLOAT - Select epochs on above total profit. + Select epochs above total profit. --max-total-profit FLOAT - Select epochs on below total profit. + Select epochs below total profit. --min-objective FLOAT - Select epochs on above objective (- is added by default). + Select epochs above objective. --max-objective FLOAT - Select epochs on below objective (- is added by default). + Select epochs below objective. --no-color Disable colorization of hyperopt results. May be useful if you are redirecting output to a file. - --print-json Print best result detailization in JSON format. + --print-json Print output in JSON format. --no-details Do not print best epoch details. + --hyperopt-filename FILENAME + Hyperopt result filename.Example: `--hyperopt- + filename=hyperopt_results_2020-09-27_16-20-48.pickle` --export-csv FILE Export to CSV-File. This will disable table print. Example: --export-csv hyperopt.csv @@ -480,7 +484,11 @@ Common arguments: --userdir PATH, --user-data-dir PATH Path to userdata directory. ``` - + +!!! Note + `hyperopt-list` will automatically use the latest available hyperopt results file. + You can override this using the `--hyperopt-filename` argument, and specify another, available filename (without path!). + ### Examples List all results, print details of the best result at the end: @@ -501,17 +509,41 @@ You can show the details of any hyperoptimization epoch previously evaluated by usage: freqtrade hyperopt-show [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--best] [--profitable] [-n INT] [--print-json] - [--no-header] + [--hyperopt-filename PATH] [--no-header] optional arguments: -h, --help show this help message and exit --best Select only best epochs. --profitable Select only profitable epochs. -n INT, --index INT Specify the index of the epoch to print details for. - --print-json Print best result detailization in JSON format. + --print-json Print output in JSON format. + --hyperopt-filename FILENAME + Hyperopt result filename.Example: `--hyperopt- + filename=hyperopt_results_2020-09-27_16-20-48.pickle` --no-header Do not print epoch details header. + +Common arguments: + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE Log to the file specified. Special values are: + 'syslog', 'journald'. See the documentation for more + details. + -V, --version show program's version number and exit + -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. + -d PATH, --datadir PATH + Path to directory with historical backtesting data. + --userdir PATH, --user-data-dir PATH + Path to userdata directory. + ``` +!!! Note + `hyperopt-show` will automatically use the latest available hyperopt results file. + You can override this using the `--hyperopt-filename` argument, and specify another, available filename (without path!). + ### Examples Print details for the epoch 168 (the number of the epoch is shown by the `hyperopt-list` subcommand or by Hyperopt itself during hyperoptimization run): diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 3f970c6eb..aa58ff585 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -27,7 +27,7 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", "use_max_market_positions", "print_all", "print_colorized", "print_json", "hyperopt_jobs", "hyperopt_random_state", "hyperopt_min_trades", - "hyperopt_continue", "hyperopt_loss"] + "hyperopt_loss"] ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"] @@ -76,10 +76,10 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_list_min_total_profit", "hyperopt_list_max_total_profit", "hyperopt_list_min_objective", "hyperopt_list_max_objective", "print_colorized", "print_json", "hyperopt_list_no_details", - "export_csv"] + "hyperoptexportfilename", "export_csv"] ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", - "print_json", "hyperopt_show_no_header"] + "print_json", "hyperoptexportfilename", "hyperopt_show_no_header"] NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes", "list-markets", "list-pairs", "list-strategies", "list-data", diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 81b8de1af..3c5775768 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -252,13 +252,6 @@ AVAILABLE_CLI_OPTIONS = { metavar='INT', default=1, ), - "hyperopt_continue": Arg( - "--continue", - help="Continue hyperopt from previous runs. " - "By default, temporary files will be removed and hyperopt will start from scratch.", - default=False, - action='store_true', - ), "hyperopt_loss": Arg( '--hyperopt-loss', help='Specify the class name of the hyperopt loss function class (IHyperOptLoss). ' @@ -270,6 +263,12 @@ AVAILABLE_CLI_OPTIONS = { metavar='NAME', default=constants.DEFAULT_HYPEROPT_LOSS, ), + "hyperoptexportfilename": Arg( + '--hyperopt-filename', + help='Hyperopt result filename.' + 'Example: `--hyperopt-filename=hyperopt_results_2020-09-27_16-20-48.pickle`', + metavar='FILENAME', + ), # List exchanges "print_one_column": Arg( '-1', '--one-column', diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index 94447e6f9..fd8f737f0 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -5,6 +5,7 @@ from typing import Any, Dict, List from colorama import init as colorama_init from freqtrade.configuration import setup_utils_configuration +from freqtrade.data.btanalysis import get_latest_hyperopt_file from freqtrade.exceptions import OperationalException from freqtrade.state import RunMode @@ -41,8 +42,9 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: 'filter_max_objective': config.get('hyperopt_list_max_objective', None), } - results_file = (config['user_data_dir'] / - 'hyperopt_results' / 'hyperopt_results.pickle') + results_file = get_latest_hyperopt_file( + config['user_data_dir'] / 'hyperopt_results', + config.get('hyperoptexportfilename')) # Previous evaluations epochs = Hyperopt.load_previous_results(results_file) @@ -81,8 +83,10 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None: print_json = config.get('print_json', False) no_header = config.get('hyperopt_show_no_header', False) - results_file = (config['user_data_dir'] / - 'hyperopt_results' / 'hyperopt_results.pickle') + results_file = get_latest_hyperopt_file( + config['user_data_dir'] / 'hyperopt_results', + config.get('hyperoptexportfilename')) + n = config.get('hyperopt_show_index', -1) filteroptions = { diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 83be28956..1ca3187fb 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -263,6 +263,9 @@ class Configuration: self._args_to_config(config, argname='hyperopt_path', logstring='Using additional Hyperopt lookup path: {}') + self._args_to_config(config, argname='hyperoptexportfilename', + logstring='Using hyperopt file: {}') + self._args_to_config(config, argname='epochs', logstring='Parameter --epochs detected ... ' 'Will run Hyperopt with for {} epochs ...' @@ -295,9 +298,6 @@ class Configuration: self._args_to_config(config, argname='hyperopt_min_trades', logstring='Parameter --min-trades detected: {}') - self._args_to_config(config, argname='hyperopt_continue', - logstring='Hyperopt continue: {}') - self._args_to_config(config, argname='hyperopt_loss', logstring='Using Hyperopt loss class name: {}') diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 887c30621..6af685712 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -22,10 +22,11 @@ BT_DATA_COLUMNS = ["pair", "profit_percent", "open_date", "close_date", "index", "open_rate", "close_rate", "open_at_end", "sell_reason"] -def get_latest_backtest_filename(directory: Union[Path, str]) -> str: +def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str: """ Get latest backtest export based on '.last_result.json'. :param directory: Directory to search for last result + :param variant: 'backtest' or 'hyperopt' - the method to return :return: string containing the filename of the latest backtest result :raises: ValueError in the following cases: * Directory does not exist @@ -45,10 +46,57 @@ def get_latest_backtest_filename(directory: Union[Path, str]) -> str: with filename.open() as file: data = json_load(file) - if 'latest_backtest' not in data: + if f'latest_{variant}' not in data: raise ValueError(f"Invalid '{LAST_BT_RESULT_FN}' format.") - return data['latest_backtest'] + return data[f'latest_{variant}'] + + +def get_latest_backtest_filename(directory: Union[Path, str]) -> str: + """ + Get latest backtest export based on '.last_result.json'. + :param directory: Directory to search for last result + :return: string containing the filename of the latest backtest result + :raises: ValueError in the following cases: + * Directory does not exist + * `directory/.last_result.json` does not exist + * `directory/.last_result.json` has the wrong content + """ + return get_latest_optimize_filename(directory, 'backtest') + + +def get_latest_hyperopt_filename(directory: Union[Path, str]) -> str: + """ + Get latest hyperopt export based on '.last_result.json'. + :param directory: Directory to search for last result + :return: string containing the filename of the latest hyperopt result + :raises: ValueError in the following cases: + * Directory does not exist + * `directory/.last_result.json` does not exist + * `directory/.last_result.json` has the wrong content + """ + try: + return get_latest_optimize_filename(directory, 'hyperopt') + except ValueError: + # Return default (legacy) pickle filename + return 'hyperopt_results.pickle' + + +def get_latest_hyperopt_file(directory: Union[Path, str], predef_filename: str = None) -> Path: + """ + Get latest hyperopt export based on '.last_result.json'. + :param directory: Directory to search for last result + :return: string containing the filename of the latest hyperopt result + :raises: ValueError in the following cases: + * Directory does not exist + * `directory/.last_result.json` does not exist + * `directory/.last_result.json` has the wrong content + """ + if isinstance(directory, str): + directory = Path(directory) + if predef_filename: + return directory / predef_filename + return directory / get_latest_hyperopt_filename(directory) def load_backtest_stats(filename: Union[Path, str]) -> Dict[str, Any]: diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 49c49c3b8..75a4e7810 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -10,6 +10,7 @@ import logging import random import warnings from collections import OrderedDict +from datetime import datetime from math import ceil from operator import itemgetter from pathlib import Path @@ -24,11 +25,11 @@ from colorama import init as colorama_init from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects from pandas import DataFrame, isna, json_normalize -from freqtrade.constants import DATETIME_PRINT_FORMAT +from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN from freqtrade.data.converter import trim_dataframe from freqtrade.data.history import get_timerange from freqtrade.exceptions import OperationalException -from freqtrade.misc import plural, round_dict +from freqtrade.misc import file_dump_json, plural, round_dict from freqtrade.optimize.backtesting import Backtesting # Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules from freqtrade.optimize.hyperopt_interface import IHyperOpt # noqa: F401 @@ -75,19 +76,16 @@ class Hyperopt: self.custom_hyperoptloss = HyperOptLossResolver.load_hyperoptloss(self.config) self.calculate_loss = self.custom_hyperoptloss.hyperopt_loss_function - + time_now = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") self.results_file = (self.config['user_data_dir'] / - 'hyperopt_results' / 'hyperopt_results.pickle') + 'hyperopt_results' / f'hyperopt_results_{time_now}.pickle') self.data_pickle_file = (self.config['user_data_dir'] / 'hyperopt_results' / 'hyperopt_tickerdata.pkl') self.total_epochs = config.get('epochs', 0) self.current_best_loss = 100 - if not self.config.get('hyperopt_continue'): - self.clean_hyperopt() - else: - logger.info("Continuing on previous hyperopt results.") + self.clean_hyperopt() self.num_epochs_saved = 0 @@ -163,6 +161,9 @@ class Hyperopt: self.num_epochs_saved = num_epochs logger.debug(f"{self.num_epochs_saved} {plural(self.num_epochs_saved, 'epoch')} " f"saved to '{self.results_file}'.") + # Store hyperopt filename + latest_filename = Path.joinpath(self.results_file.parent, LAST_BT_RESULT_FN) + file_dump_json(latest_filename, {'latest_hyperopt': str(self.results_file.name)}) @staticmethod def _read_results(results_file: Path) -> List: @@ -655,8 +656,6 @@ class Hyperopt: self.backtesting.strategy.dp = None # type: ignore IStrategy.dp = None # type: ignore - self.epochs = self.load_previous_results(self.results_file) - cpus = cpu_count() logger.info(f"Found {cpus} CPU cores. Let's make them scream!") config_jobs = self.config.get('hyperopt_jobs', -1) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index a6270e92d..7696dd96a 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -11,7 +11,8 @@ from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelis calculate_market_change, calculate_max_drawdown, combine_dataframes_with_mean, create_cum_profit, extract_trades_of_period, get_latest_backtest_filename, - load_backtest_data, load_trades, load_trades_from_db) + get_latest_hyperopt_file, load_backtest_data, load_trades, + load_trades_from_db) from freqtrade.data.history import load_data, load_pair_history from freqtrade.optimize.backtesting import BacktestResult from tests.conftest import create_mock_trades @@ -38,6 +39,17 @@ def test_get_latest_backtest_filename(testdatadir, mocker): get_latest_backtest_filename(testdatadir) +def test_get_latest_hyperopt_file(testdatadir, mocker): + res = get_latest_hyperopt_file(testdatadir / 'does_not_exist', 'testfile.pickle') + assert res == testdatadir / 'does_not_exist/testfile.pickle' + + res = get_latest_hyperopt_file(testdatadir.parent) + assert res == testdatadir.parent / "hyperopt_results.pickle" + + res = get_latest_hyperopt_file(str(testdatadir.parent)) + assert res == testdatadir.parent / "hyperopt_results.pickle" + + def test_load_backtest_data_old_format(testdatadir): filename = testdatadir / "backtest-result_test.json" diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index bf8a720b5..7f0e0eb6d 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -5,7 +5,7 @@ from copy import deepcopy from datetime import datetime from pathlib import Path from typing import Dict, List -from unittest.mock import MagicMock, PropertyMock +from unittest.mock import MagicMock import pandas as pd import pytest @@ -79,13 +79,14 @@ def create_results(mocker, hyperopt, testdatadir) -> List[Dict]: mocker.patch.object(Path, "is_file", MagicMock(return_value=False)) stat_mock = MagicMock() - stat_mock.st_size = PropertyMock(return_value=1) - mocker.patch.object(Path, "stat", MagicMock(return_value=False)) + stat_mock.st_size = 1 + mocker.patch.object(Path, "stat", MagicMock(return_value=stat_mock)) mocker.patch.object(Path, "unlink", MagicMock(return_value=True)) mocker.patch('freqtrade.optimize.hyperopt.dump', return_value=None) + mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') - return [{'loss': 1, 'result': 'foo', 'params': {}}] + return [{'loss': 1, 'result': 'foo', 'params': {}, 'is_best': True}] def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None: @@ -495,6 +496,7 @@ def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None: def test_save_results_saves_epochs(mocker, hyperopt, testdatadir, caplog) -> None: epochs = create_results(mocker, hyperopt, testdatadir) mock_dump = mocker.patch('freqtrade.optimize.hyperopt.dump', return_value=None) + mock_dump_json = mocker.patch('freqtrade.optimize.hyperopt.file_dump_json', return_value=None) results_file = testdatadir / 'optimize' / 'ut_results.pickle' caplog.set_level(logging.DEBUG) @@ -503,6 +505,7 @@ def test_save_results_saves_epochs(mocker, hyperopt, testdatadir, caplog) -> Non hyperopt._save_results() assert log_has(f"1 epoch saved to '{results_file}'.", caplog) mock_dump.assert_called_once() + mock_dump_json.assert_called_once() hyperopt.epochs = epochs + epochs hyperopt._save_results() @@ -519,6 +522,28 @@ def test_read_results_returns_epochs(mocker, hyperopt, testdatadir, caplog) -> N mock_load.assert_called_once() +def test_load_previous_results(mocker, hyperopt, testdatadir, caplog) -> None: + epochs = create_results(mocker, hyperopt, testdatadir) + mock_load = mocker.patch('freqtrade.optimize.hyperopt.load', return_value=epochs) + mocker.patch.object(Path, 'is_file', MagicMock(return_value=True)) + statmock = MagicMock() + statmock.st_size = 5 + # mocker.patch.object(Path, 'stat', MagicMock(return_value=statmock)) + + results_file = testdatadir / 'optimize' / 'ut_results.pickle' + + hyperopt_epochs = hyperopt.load_previous_results(results_file) + + assert hyperopt_epochs == epochs + mock_load.assert_called_once() + + del epochs[0]['is_best'] + mock_load = mocker.patch('freqtrade.optimize.hyperopt.load', return_value=epochs) + + with pytest.raises(OperationalException): + hyperopt.load_previous_results(results_file) + + def test_roi_table_generation(hyperopt) -> None: params = { 'roi_t1': 5, @@ -534,6 +559,8 @@ def test_roi_table_generation(hyperopt) -> None: def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None: dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) + mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') + mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', MagicMock(return_value=(MagicMock(), None))) mocker.patch( @@ -837,19 +864,10 @@ def test_clean_hyperopt(mocker, hyperopt_conf, caplog): assert log_has(f"Removing `{h.data_pickle_file}`.", caplog) -def test_continue_hyperopt(mocker, hyperopt_conf, caplog): - patch_exchange(mocker) - hyperopt_conf.update({'hyperopt_continue': True}) - mocker.patch("freqtrade.optimize.hyperopt.Path.is_file", MagicMock(return_value=True)) - unlinkmock = mocker.patch("freqtrade.optimize.hyperopt.Path.unlink", MagicMock()) - Hyperopt(hyperopt_conf) - - assert unlinkmock.call_count == 0 - assert log_has("Continuing on previous hyperopt results.", caplog) - - def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None: dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) + mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') + mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', MagicMock(return_value=(MagicMock(), None))) mocker.patch( @@ -905,6 +923,7 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None: def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None: dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) + mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', MagicMock(return_value=(MagicMock(), None))) mocker.patch( @@ -952,6 +971,7 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None: def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None: dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) + mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', MagicMock(return_value=(MagicMock(), None))) mocker.patch( @@ -998,6 +1018,7 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None: def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> None: dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) + mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', MagicMock(return_value=(MagicMock(), None))) mocker.patch( @@ -1050,6 +1071,7 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non def test_simplified_interface_all_failed(mocker, hyperopt_conf) -> None: mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) + mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', MagicMock(return_value=(MagicMock(), None))) mocker.patch( @@ -1076,6 +1098,7 @@ def test_simplified_interface_all_failed(mocker, hyperopt_conf) -> None: def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None: dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) + mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', MagicMock(return_value=(MagicMock(), None))) mocker.patch( @@ -1128,6 +1151,7 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None: def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None: dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) + mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', MagicMock(return_value=(MagicMock(), None))) mocker.patch( @@ -1186,6 +1210,7 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None: ]) def test_simplified_interface_failed(mocker, hyperopt_conf, method, space) -> None: mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) + mocker.patch('freqtrade.optimize.hyperopt.file_dump_json') mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data', MagicMock(return_value=(MagicMock(), None))) mocker.patch( diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index aee88104b..c62282cf0 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1138,9 +1138,9 @@ def test_telegram_logs(default_conf, update, mocker) -> None: context = MagicMock() context.args = [] telegram._logs(update=update, context=context) - # Called at least 3 times. Exact times will change with unrelated changes to setup messages + # Called at least 2 times. Exact times will change with unrelated changes to setup messages # Therefore we don't test for this explicitly. - assert msg_mock.call_count > 3 + assert msg_mock.call_count >= 2 def test_edge_disabled(default_conf, update, mocker) -> None: