From 72d197a99d0570ae9d3607042d7ec5a533907c8e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 11 Sep 2022 15:42:27 +0200 Subject: [PATCH 1/3] Run first epoch in non-parallel mode this allows dataprovider to load it's cache. closes #7384 --- freqtrade/data/dataprovider.py | 4 +++- freqtrade/optimize/hyperopt.py | 21 +++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index e639b3ae7..c6519d2b8 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -196,7 +196,9 @@ class DataProvider: Clear pair dataframe cache. """ self.__cached_pairs = {} - self.__cached_pairs_backtesting = {} + # Don't reset backtesting pairs - + # otherwise they're reloaded each time during hyperopt due to with analyze_per_epoch + # self.__cached_pairs_backtesting = {} self.__slice_index = 0 # Exchange functions diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 76fc84609..b0119368f 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -580,11 +580,24 @@ class Hyperopt: max_value=self.total_epochs, redirect_stdout=False, redirect_stderr=False, widgets=widgets ) as pbar: - EVALS = ceil(self.total_epochs / jobs) - for i in range(EVALS): + start = 0 + + if self.analyze_per_epoch: + # First analysis not in parallel mode when using --analyze-per-epoch. + # This allows dataprovider to load it's informative cache. + asked, is_random = self.get_asked_points(n_points=1) + # print(asked) + f_val = self.generate_optimizer(asked[0]) + self.opt.tell(asked, [f_val['loss']]) + self.evaluate_result(f_val, 1, is_random[0]) + pbar.update(1) + start += 1 + + evals = ceil((self.total_epochs - start) / jobs) + for i in range(evals): # Correct the number of epochs to be processed for the last # iteration (should not exceed self.total_epochs in total) - n_rest = (i + 1) * jobs - self.total_epochs + n_rest = (i + 1) * jobs - (self.total_epochs - start) current_jobs = jobs - n_rest if n_rest > 0 else jobs asked, is_random = self.get_asked_points(n_points=current_jobs) @@ -594,7 +607,7 @@ class Hyperopt: # Calculate progressbar outputs for j, val in enumerate(f_val): # Use human-friendly indexes here (starting from 1) - current = i * jobs + j + 1 + current = i * jobs + j + 1 + start self.evaluate_result(val, current, is_random[j]) From 816c1f760373f6fc55710cb2e3f09ae39eb14fc5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 11 Sep 2022 17:51:30 +0200 Subject: [PATCH 2/3] add test for per epoch hyperopt --- tests/optimize/test_hyperopt.py | 39 +++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 0f615b7a3..eaea8aee7 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -922,6 +922,45 @@ def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmpdir, hyperopt.start() +def test_in_strategy_auto_hyperopt_per_epoch(mocker, hyperopt_conf, tmpdir, fee) -> None: + patch_exchange(mocker) + mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) + (Path(tmpdir) / 'hyperopt_results').mkdir(parents=True) + + hyperopt_conf.update({ + 'strategy': 'HyperoptableStrategy', + 'user_data_dir': Path(tmpdir), + 'hyperopt_random_state': 42, + 'spaces': ['all'], + 'epochs': 3, + 'analyze_per_epoch': True, + }) + go = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.generate_optimizer', + return_value={ + 'loss': 0.05, + 'results_explanation': 'foo result', 'params': {}, + 'results_metrics': generate_result_metrics(), + }) + hyperopt = Hyperopt(hyperopt_conf) + hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0) + assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) + assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter) + assert hyperopt.backtesting.strategy.bot_loop_started is True + + assert hyperopt.backtesting.strategy.buy_rsi.in_space is True + assert hyperopt.backtesting.strategy.buy_rsi.value == 35 + assert hyperopt.backtesting.strategy.sell_rsi.value == 74 + assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value == 30 + buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range + assert isinstance(buy_rsi_range, range) + # Range from 0 - 50 (inclusive) + assert len(list(buy_rsi_range)) == 51 + + hyperopt.start() + # backtesting should be called 3 times (once per epoch) + assert go.call_count == 3 + + def test_SKDecimal(): space = SKDecimal(1, 2, decimals=2) assert 1.5 in space From 982c0315fa2ba909362173c2e892aa7bca2c836b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 11 Sep 2022 19:31:11 +0200 Subject: [PATCH 3/3] Rename variable --- freqtrade/optimize/hyperopt.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index b0119368f..f15e0b7d8 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -586,10 +586,9 @@ class Hyperopt: # First analysis not in parallel mode when using --analyze-per-epoch. # This allows dataprovider to load it's informative cache. asked, is_random = self.get_asked_points(n_points=1) - # print(asked) - f_val = self.generate_optimizer(asked[0]) - self.opt.tell(asked, [f_val['loss']]) - self.evaluate_result(f_val, 1, is_random[0]) + f_val0 = self.generate_optimizer(asked[0]) + self.opt.tell(asked, [f_val0['loss']]) + self.evaluate_result(f_val0, 1, is_random[0]) pbar.update(1) start += 1