From 16a516a882f8936df3e4191e0311cedbc9d7a120 Mon Sep 17 00:00:00 2001 From: Italo Date: Wed, 19 Jan 2022 01:50:15 +0000 Subject: [PATCH 01/32] added plot functionality --- freqtrade/optimize/hyperopt.py | 65 +++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 209edd157..cfbc3ea82 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -32,6 +32,11 @@ from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F4 from freqtrade.optimize.hyperopt_tools import HyperoptTools, hyperopt_serializer from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver +from skopt.plots import plot_convergence, plot_regret, plot_evaluations, plot_objective +import matplotlib.pyplot as plt +import numpy as np +import random +from sklearn.base import clone # Suppress scikit-learn FutureWarnings from skopt @@ -476,7 +481,12 @@ class Hyperopt: asked = self.opt.ask(n_points=current_jobs) f_val = self.run_optimizer_parallel(parallel, asked, i) - self.opt.tell(asked, [v['loss'] for v in f_val]) + res = self.opt.tell(asked, [v['loss'] for v in f_val]) + + self.plot_optimizer(res, path='user_data/scripts', convergence=False, regret=False, mse=True, objective=True, jobs=jobs) + + if res.models and hasattr(res.models[-1], "kernel_"): + print(f'kernel: {res.models[-1].kernel_}') # Calculate progressbar outputs for j, val in enumerate(f_val): @@ -521,3 +531,56 @@ class Hyperopt: # This is printed when Ctrl+C is pressed quickly, before first epochs have # a chance to be evaluated. print("No epochs evaluated yet, no best result.") + + def plot_mse(self, res, ax, jobs): + if len(res.x_iters) < 10: + return + + if not hasattr(self, 'mse_list'): + self.mse_list = [] + + model = clone(res.models[-1]) + i_subset = random.sample(range(len(res.x_iters)), 100) if len(res.x_iters) > 100 else range(len(res.x_iters)) + + i_train = random.sample(i_subset, round(.8*len(i_subset))) # get 80% random indices + x_train = [x for i, x in enumerate(res.x_iters) if i in i_train] + y_train = [y for i, y in enumerate(res.func_vals) if i in i_train] + + i_test = [i for i in i_subset if i not in i_train] # get 20% random indices + x_test = [x for i, x in enumerate(res.x_iters) if i in i_test] + y_test = [y for i, y in enumerate(res.func_vals) if i in i_test] + model.fit(np.array(x_train), np.array(y_train)) + y_pred, sigma = model.predict(np.array(x_test), return_std=True) + mse = np.mean((y_test - y_pred) ** 2) + self.mse_list.append(mse) + + ax.plot(range(INITIAL_POINTS, INITIAL_POINTS + jobs * len(self.mse_list), jobs), self.mse_list, label='MSE', marker=".", markersize=12, lw=2) + + def plot_optimizer(self, res, path, jobs, convergence=True, regret=True, evaluations=True, objective=True, mse=True): + path = Path(path) + if convergence: + ax = plot_convergence(res) + ax.flatten()[0].figure.savefig(path / 'convergence.png') + + if regret: + ax = plot_regret(res) + ax.flatten()[0].figure.savefig(path / 'regret.png') + + if evaluations: +# print('evaluations') + ax = plot_evaluations(res) + ax.flatten()[0].figure.savefig(path / 'evaluations.png') + + if objective and res.models: +# print('objective') + ax = plot_objective(res, sample_source='result', n_samples=50, n_points=10) + ax.flatten()[0].figure.savefig(path / 'objective.png') + + if mse and res.models: +# print('mse') + fig, ax = plt.subplots() + ax.set_ylabel('MSE') + ax.set_xlabel('Epoch') + ax.set_title('MSE') + ax = self.plot_mse(res, ax, jobs) + fig.savefig(path / 'mse.png') From 2eec51bfcbdc27279b21390cc5fa3ee7069233f3 Mon Sep 17 00:00:00 2001 From: Italo Date: Wed, 19 Jan 2022 02:00:14 +0000 Subject: [PATCH 02/32] Update requirements-hyperopt.txt --- requirements-hyperopt.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 122243bf2..57bb25e2c 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -8,3 +8,4 @@ scikit-optimize==0.9.0 filelock==3.4.2 joblib==1.1.0 progressbar2==4.0.0 +matplotlib \ No newline at end of file From 52206e6f41926ef89f7d9c051c377de9fc16f7ff Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Thu, 20 Jan 2022 17:15:05 +0000 Subject: [PATCH 03/32] add buy tag to plot --- freqtrade/plot/plotting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 3769d4c5a..b8a747105 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -236,6 +236,7 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots: if trades is not None and len(trades) > 0: # Create description for sell summarizing the trade trades['desc'] = trades.apply(lambda row: f"{row['profit_ratio']:.2%}, " + f"{row['buy_tag']}, " f"{row['sell_reason']}, " f"{row['trade_duration']} min", axis=1) From 0ce6c150ff6e1dfdba0a3a7bdda97288fd77eaa0 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Sat, 22 Jan 2022 14:06:45 +0000 Subject: [PATCH 04/32] set stoploss at trade creation --- freqtrade/optimize/backtesting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index ae4001f5f..9cfeedd75 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -521,6 +521,7 @@ class Backtesting: exchange='backtesting', orders=[] ) + trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True) order = Order( ft_is_open=False, From a2fb241a3b210f100db025815fb3542410476b45 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Mon, 24 Jan 2022 01:35:42 +0000 Subject: [PATCH 05/32] increase initial points to 64 --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index cfbc3ea82..f49f3f307 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -50,7 +50,7 @@ progressbar.streams.wrap_stdout() logger = logging.getLogger(__name__) -INITIAL_POINTS = 30 +INITIAL_POINTS = 64 # Keep no more than SKOPT_MODEL_QUEUE_SIZE models # in the skopt model queue, to optimize memory consumption From 992eac9efaf8c7bdc98abd0c350ccb8930555cd0 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Sat, 5 Feb 2022 17:36:19 +0000 Subject: [PATCH 06/32] Update hyperopt.py --- freqtrade/optimize/hyperopt.py | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 60c54fe40..5e59135bd 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -50,7 +50,7 @@ progressbar.streams.wrap_stdout() logger = logging.getLogger(__name__) -INITIAL_POINTS = 64 +INITIAL_POINTS = 32 # Keep no more than SKOPT_MODEL_QUEUE_SIZE models # in the skopt model queue, to optimize memory consumption @@ -532,7 +532,8 @@ class Hyperopt: # a chance to be evaluated. print("No epochs evaluated yet, no best result.") - def plot_mse(self, res, ax, jobs): + def plot_mse(self, res, ax, jobs): + from sklearn.model_selection import cross_val_score if len(res.x_iters) < 10: return @@ -540,19 +541,26 @@ class Hyperopt: self.mse_list = [] model = clone(res.models[-1]) - i_subset = random.sample(range(len(res.x_iters)), 100) if len(res.x_iters) > 100 else range(len(res.x_iters)) + # i_subset = random.sample(range(len(res.x_iters)), 100) if len(res.x_iters) > 100 else range(len(res.x_iters)) - i_train = random.sample(i_subset, round(.8*len(i_subset))) # get 80% random indices - x_train = [x for i, x in enumerate(res.x_iters) if i in i_train] - y_train = [y for i, y in enumerate(res.func_vals) if i in i_train] + # i_train = random.sample(i_subset, round(.8*len(i_subset))) # get 80% random indices + # x_train = [x for i, x in enumerate(res.x_iters) if i in i_train] + # y_train = [y for i, y in enumerate(res.func_vals) if i in i_train] - i_test = [i for i in i_subset if i not in i_train] # get 20% random indices - x_test = [x for i, x in enumerate(res.x_iters) if i in i_test] - y_test = [y for i, y in enumerate(res.func_vals) if i in i_test] - model.fit(np.array(x_train), np.array(y_train)) - y_pred, sigma = model.predict(np.array(x_test), return_std=True) - mse = np.mean((y_test - y_pred) ** 2) - self.mse_list.append(mse) + # i_test = [i for i in i_subset if i not in i_train] # get 20% random indices + # x_test = [x for i, x in enumerate(res.x_iters) if i in i_test] + # y_test = [y for i, y in enumerate(res.func_vals) if i in i_test] + model.fit(res.x_iters, res.func_vals) + # Perform a cross-validation estimate of the coefficient of determination using + # the cross_validation module using all CPUs available on the machine + # K = 5 # folds + R2 = cross_val_score(model, X=res.x_iters, y=res.func_vals, cv=5, n_jobs=jobs).mean() + print(f'R2: {R2}') + R2 = R2 if R2 > -5 else -5 + self.mse_list.append(R2) + # y_pred, sigma = model.predict(np.array(x_test), return_std=True) + # mse = np.mean((y_test - y_pred) ** 2) + # self.mse_list.append(mse) ax.plot(range(INITIAL_POINTS, INITIAL_POINTS + jobs * len(self.mse_list), jobs), self.mse_list, label='MSE', marker=".", markersize=12, lw=2) From 6a4cae1f8c2f7569dff9156431e36dd1162810b9 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Sun, 6 Feb 2022 00:17:48 +0000 Subject: [PATCH 07/32] Update hyperopt.py --- freqtrade/optimize/hyperopt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 5e59135bd..25055d06c 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -540,7 +540,7 @@ class Hyperopt: if not hasattr(self, 'mse_list'): self.mse_list = [] - model = clone(res.models[-1]) + # model = clone(res.models[-1]) # i_subset = random.sample(range(len(res.x_iters)), 100) if len(res.x_iters) > 100 else range(len(res.x_iters)) # i_train = random.sample(i_subset, round(.8*len(i_subset))) # get 80% random indices @@ -550,11 +550,11 @@ class Hyperopt: # i_test = [i for i in i_subset if i not in i_train] # get 20% random indices # x_test = [x for i, x in enumerate(res.x_iters) if i in i_test] # y_test = [y for i, y in enumerate(res.func_vals) if i in i_test] - model.fit(res.x_iters, res.func_vals) + # model.fit(res.x_iters, res.func_vals) # Perform a cross-validation estimate of the coefficient of determination using # the cross_validation module using all CPUs available on the machine # K = 5 # folds - R2 = cross_val_score(model, X=res.x_iters, y=res.func_vals, cv=5, n_jobs=jobs).mean() + R2 = cross_val_score(res.models[-1], X=res.x_iters, y=res.func_vals, cv=5, n_jobs=jobs).mean() print(f'R2: {R2}') R2 = R2 if R2 > -5 else -5 self.mse_list.append(R2) From 6c1729e20b6ea87d341ad8b2e204409c7ded5be6 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Sun, 6 Feb 2022 01:07:30 +0000 Subject: [PATCH 08/32] ignore warnings --- freqtrade/optimize/hyperopt.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 25055d06c..d99c2b3b0 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -554,7 +554,9 @@ class Hyperopt: # Perform a cross-validation estimate of the coefficient of determination using # the cross_validation module using all CPUs available on the machine # K = 5 # folds - R2 = cross_val_score(res.models[-1], X=res.x_iters, y=res.func_vals, cv=5, n_jobs=jobs).mean() + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + R2 = cross_val_score(res.models[-1], X=res.x_iters, y=res.func_vals, cv=5, n_jobs=jobs).mean() print(f'R2: {R2}') R2 = R2 if R2 > -5 else -5 self.mse_list.append(R2) From adf8f6b2d503649effaed161e81108df10483a76 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Sun, 6 Feb 2022 10:33:49 +0000 Subject: [PATCH 09/32] Update hyperopt.py --- freqtrade/optimize/hyperopt.py | 56 +++++++++++----------------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index d99c2b3b0..19132a14a 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -32,18 +32,17 @@ from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F4 from freqtrade.optimize.hyperopt_tools import HyperoptTools, hyperopt_serializer from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver -from skopt.plots import plot_convergence, plot_regret, plot_evaluations, plot_objective import matplotlib.pyplot as plt import numpy as np import random -from sklearn.base import clone - # Suppress scikit-learn FutureWarnings from skopt with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=FutureWarning) from skopt import Optimizer from skopt.space import Dimension + from sklearn.model_selection import cross_val_score + from skopt.plots import plot_convergence, plot_regret, plot_evaluations, plot_objective progressbar.streams.wrap_stderr() progressbar.streams.wrap_stdout() @@ -483,7 +482,7 @@ class Hyperopt: f_val = self.run_optimizer_parallel(parallel, asked, i) res = self.opt.tell(asked, [v['loss'] for v in f_val]) - self.plot_optimizer(res, path='user_data/scripts', convergence=False, regret=False, mse=True, objective=True, jobs=jobs) + self.plot_optimizer(res, path='user_data/scripts', convergence=False, regret=False, r2=True, objective=True, jobs=jobs) if res.models and hasattr(res.models[-1], "kernel_"): print(f'kernel: {res.models[-1].kernel_}') @@ -532,41 +531,21 @@ class Hyperopt: # a chance to be evaluated. print("No epochs evaluated yet, no best result.") - def plot_mse(self, res, ax, jobs): - from sklearn.model_selection import cross_val_score + def plot_r2(self, res, ax, jobs): if len(res.x_iters) < 10: return - if not hasattr(self, 'mse_list'): - self.mse_list = [] + if not hasattr(self, 'r2_list'): + self.r2_list = [] - # model = clone(res.models[-1]) - # i_subset = random.sample(range(len(res.x_iters)), 100) if len(res.x_iters) > 100 else range(len(res.x_iters)) - - # i_train = random.sample(i_subset, round(.8*len(i_subset))) # get 80% random indices - # x_train = [x for i, x in enumerate(res.x_iters) if i in i_train] - # y_train = [y for i, y in enumerate(res.func_vals) if i in i_train] - - # i_test = [i for i in i_subset if i not in i_train] # get 20% random indices - # x_test = [x for i, x in enumerate(res.x_iters) if i in i_test] - # y_test = [y for i, y in enumerate(res.func_vals) if i in i_test] - # model.fit(res.x_iters, res.func_vals) - # Perform a cross-validation estimate of the coefficient of determination using - # the cross_validation module using all CPUs available on the machine - # K = 5 # folds - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - R2 = cross_val_score(res.models[-1], X=res.x_iters, y=res.func_vals, cv=5, n_jobs=jobs).mean() - print(f'R2: {R2}') - R2 = R2 if R2 > -5 else -5 - self.mse_list.append(R2) - # y_pred, sigma = model.predict(np.array(x_test), return_std=True) - # mse = np.mean((y_test - y_pred) ** 2) - # self.mse_list.append(mse) + r2 = cross_val_score(res.models[-1], X=res.x_iters, y=res.func_vals, scoring='r2', cv=5, n_jobs=jobs).mean() + print(f'R2: {r2}') + r2 = r2 if r2 > -5 else -5 + self.r2_list.append(r2) - ax.plot(range(INITIAL_POINTS, INITIAL_POINTS + jobs * len(self.mse_list), jobs), self.mse_list, label='MSE', marker=".", markersize=12, lw=2) + ax.plot(range(INITIAL_POINTS, INITIAL_POINTS + jobs * len(self.r2_list), jobs), self.r2_list, label='R2', marker=".", markersize=12, lw=2) - def plot_optimizer(self, res, path, jobs, convergence=True, regret=True, evaluations=True, objective=True, mse=True): + def plot_optimizer(self, res, path, jobs, convergence=True, regret=True, evaluations=True, objective=True, r2=True): path = Path(path) if convergence: ax = plot_convergence(res) @@ -586,11 +565,10 @@ class Hyperopt: ax = plot_objective(res, sample_source='result', n_samples=50, n_points=10) ax.flatten()[0].figure.savefig(path / 'objective.png') - if mse and res.models: -# print('mse') + if r2 and res.models: fig, ax = plt.subplots() - ax.set_ylabel('MSE') + ax.set_ylabel('R2') ax.set_xlabel('Epoch') - ax.set_title('MSE') - ax = self.plot_mse(res, ax, jobs) - fig.savefig(path / 'mse.png') + ax.set_title('R2') + ax = self.plot_r2(res, ax, jobs) + fig.savefig(path / 'r2.png') From d03378b1df7c76b7d1931c174af9197abf977357 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Sun, 6 Feb 2022 15:32:59 +0000 Subject: [PATCH 10/32] Update hyperopt.py --- freqtrade/optimize/hyperopt.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 19132a14a..ba32943cb 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -538,7 +538,10 @@ class Hyperopt: if not hasattr(self, 'r2_list'): self.r2_list = [] - r2 = cross_val_score(res.models[-1], X=res.x_iters, y=res.func_vals, scoring='r2', cv=5, n_jobs=jobs).mean() + model = res.models[-1] + model.criterion = 'squared_error' + + r2 = cross_val_score(model, X=res.x_iters, y=res.func_vals, scoring='r2', cv=5, n_jobs=jobs).mean() print(f'R2: {r2}') r2 = r2 if r2 > -5 else -5 self.r2_list.append(r2) From d2a54483050a587facad827afa6d3177cd68d702 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Fri, 11 Mar 2022 17:38:32 +0000 Subject: [PATCH 11/32] Update hyperopt.py --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index ba32943cb..aa255967e 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -486,6 +486,7 @@ class Hyperopt: if res.models and hasattr(res.models[-1], "kernel_"): print(f'kernel: {res.models[-1].kernel_}') + print(datetime.now()) # Calculate progressbar outputs for j, val in enumerate(f_val): @@ -542,7 +543,6 @@ class Hyperopt: model.criterion = 'squared_error' r2 = cross_val_score(model, X=res.x_iters, y=res.func_vals, scoring='r2', cv=5, n_jobs=jobs).mean() - print(f'R2: {r2}') r2 = r2 if r2 > -5 else -5 self.r2_list.append(r2) From d796ce09352042160023235a404899d2973a55c7 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Sun, 20 Mar 2022 15:41:14 +0000 Subject: [PATCH 12/32] Update hyperopt.py 1. Try to get points using `self.opt.ask` first 2. Discard the points that have already been evaluated 3. Retry using `self.opt.ask` up to 3 times 4. If still some points are missing in respect to `n_points`, random sample some points 5. Repeat until at least `n_points` points in the `asked_non_tried` list 6. Return a list with legth truncated at `n_points` --- freqtrade/optimize/hyperopt.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index aa255967e..badaf2b3c 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -413,6 +413,31 @@ class Hyperopt: f'({(self.max_date - self.min_date).days} days)..') # Store non-trimmed data - will be trimmed after signal generation. dump(preprocessed, self.data_pickle_file) + + def get_asked_points(self, n_points: int) -> List[Any]: + ''' + Steps: + 1. Try to get points using `self.opt.ask` first + 2. Discard the points that have already been evaluated + 3. Retry using `self.opt.ask` up to 3 times + 4. If still some points are missing in respect to `n_points`, random sample some points + 5. Repeat until at least `n_points` points in the `asked_non_tried` list + 6. Return a list with legth truncated at `n_points` + ''' + i = 0 + asked_non_tried = [] + while i < 100: + if len(asked_non_tried) < n_points: + if i < 3: + asked = self.opt.ask(n_points=n_points) + else: + # use random sample if `self.opt.ask` returns points points already tried + asked = self.opt.space.rvs(n_samples=n_points * 5) + asked_non_tried += [x for x in asked if x not in self.opt.Xi and x not in asked_non_tried] + i += 1 + else: + break + return asked_non_tried[:n_points] def start(self) -> None: self.random_state = self._set_random_state(self.config.get('hyperopt_random_state', None)) @@ -478,11 +503,11 @@ class Hyperopt: n_rest = (i + 1) * jobs - self.total_epochs current_jobs = jobs - n_rest if n_rest > 0 else jobs - asked = self.opt.ask(n_points=current_jobs) + asked = self.get_asked_points(n_points=current_jobs) f_val = self.run_optimizer_parallel(parallel, asked, i) res = self.opt.tell(asked, [v['loss'] for v in f_val]) - self.plot_optimizer(res, path='user_data/scripts', convergence=False, regret=False, r2=True, objective=True, jobs=jobs) + self.plot_optimizer(res, path='user_data/scripts', convergence=False, regret=False, r2=False, objective=True, jobs=jobs) if res.models and hasattr(res.models[-1], "kernel_"): print(f'kernel: {res.models[-1].kernel_}') From e16bb1b34e381b9bb82b47d9e406f093479c5ffa Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Sun, 20 Mar 2022 16:02:03 +0000 Subject: [PATCH 13/32] Optimize only new points Enforce points returned from `self.opt.ask` have not been already evaluated --- freqtrade/optimize/hyperopt.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 9664e6f07..8b6225fa7 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -410,6 +410,35 @@ class Hyperopt: # Store non-trimmed data - will be trimmed after signal generation. dump(preprocessed, self.data_pickle_file) + def get_asked_points(self, n_points: int) -> List[List[Any]]: + ''' + Enforce points returned from `self.opt.ask` have not been already evaluated + + Steps: + 1. Try to get points using `self.opt.ask` first + 2. Discard the points that have already been evaluated + 3. Retry using `self.opt.ask` up to 3 times + 4. If still some points are missing in respect to `n_points`, random sample some points + 5. Repeat until at least `n_points` points in the `asked_non_tried` list + 6. Return a list with legth truncated at `n_points` + ''' + i = 0 + asked_non_tried: List[List[Any]] = [] + while i < 100: + if len(asked_non_tried) < n_points: + if i < 3: + asked = self.opt.ask(n_points=n_points) + else: + # use random sample if `self.opt.ask` returns points points already tried + asked = self.opt.space.rvs(n_samples=n_points * 5) + asked_non_tried += [x for x in asked + if x not in self.opt.Xi + and x not in asked_non_tried] + i += 1 + else: + break + return asked_non_tried[:n_points] + def start(self) -> None: self.random_state = self._set_random_state(self.config.get('hyperopt_random_state', None)) logger.info(f"Using optimizer random state: {self.random_state}") @@ -474,7 +503,7 @@ class Hyperopt: n_rest = (i + 1) * jobs - self.total_epochs current_jobs = jobs - n_rest if n_rest > 0 else jobs - asked = self.opt.ask(n_points=current_jobs) + asked = self.get_asked_points(n_points=current_jobs) f_val = self.run_optimizer_parallel(parallel, asked, i) self.opt.tell(asked, [v['loss'] for v in f_val]) From 0fd269e4f00feefe431a16f2fd45f4fd113dc5e7 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Sun, 20 Mar 2022 16:03:07 +0000 Subject: [PATCH 14/32] typo --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 8b6225fa7..55ae44b91 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -420,7 +420,7 @@ class Hyperopt: 3. Retry using `self.opt.ask` up to 3 times 4. If still some points are missing in respect to `n_points`, random sample some points 5. Repeat until at least `n_points` points in the `asked_non_tried` list - 6. Return a list with legth truncated at `n_points` + 6. Return a list with length truncated at `n_points` ''' i = 0 asked_non_tried: List[List[Any]] = [] From 23f1a1904bfa90d2fa00865e71023f869479adfe Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Sun, 20 Mar 2022 16:06:41 +0000 Subject: [PATCH 15/32] more compact --- freqtrade/optimize/hyperopt.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 55ae44b91..61e8913df 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -424,19 +424,15 @@ class Hyperopt: ''' i = 0 asked_non_tried: List[List[Any]] = [] - while i < 100: - if len(asked_non_tried) < n_points: - if i < 3: - asked = self.opt.ask(n_points=n_points) - else: - # use random sample if `self.opt.ask` returns points points already tried - asked = self.opt.space.rvs(n_samples=n_points * 5) - asked_non_tried += [x for x in asked - if x not in self.opt.Xi - and x not in asked_non_tried] - i += 1 + while i < 100 and len(asked_non_tried) < n_points: + if i < 3: + asked = self.opt.ask(n_points=n_points) else: - break + asked = self.opt.space.rvs(n_samples=n_points * 5) + asked_non_tried += [x for x in asked + if x not in self.opt.Xi + and x not in asked_non_tried] + i += 1 return asked_non_tried[:n_points] def start(self) -> None: From f8a674f24de013853e4b5acee075fc23b23bd64b Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Sun, 20 Mar 2022 16:08:38 +0000 Subject: [PATCH 16/32] make robust in case all points have been tried --- freqtrade/optimize/hyperopt.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 61e8913df..c1adbf45e 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -433,7 +433,10 @@ class Hyperopt: if x not in self.opt.Xi and x not in asked_non_tried] i += 1 - return asked_non_tried[:n_points] + if asked_non_tried: + return asked_non_tried[:n_points] + else: + return self.opt.ask(n_points=n_points) def start(self) -> None: self.random_state = self._set_random_state(self.config.get('hyperopt_random_state', None)) From fca93d8dfec22dad88496774c8482d07d25ca325 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Sun, 20 Mar 2022 16:12:06 +0000 Subject: [PATCH 17/32] Update hyperopt.py --- freqtrade/optimize/hyperopt.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index badaf2b3c..bbdc8bf27 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -414,30 +414,33 @@ class Hyperopt: # Store non-trimmed data - will be trimmed after signal generation. dump(preprocessed, self.data_pickle_file) - def get_asked_points(self, n_points: int) -> List[Any]: + def get_asked_points(self, n_points: int) -> List[List[Any]]: ''' + Enforce points returned from `self.opt.ask` have not been already evaluated + Steps: 1. Try to get points using `self.opt.ask` first 2. Discard the points that have already been evaluated 3. Retry using `self.opt.ask` up to 3 times 4. If still some points are missing in respect to `n_points`, random sample some points 5. Repeat until at least `n_points` points in the `asked_non_tried` list - 6. Return a list with legth truncated at `n_points` + 6. Return a list with length truncated at `n_points` ''' i = 0 - asked_non_tried = [] - while i < 100: - if len(asked_non_tried) < n_points: - if i < 3: - asked = self.opt.ask(n_points=n_points) - else: - # use random sample if `self.opt.ask` returns points points already tried - asked = self.opt.space.rvs(n_samples=n_points * 5) - asked_non_tried += [x for x in asked if x not in self.opt.Xi and x not in asked_non_tried] - i += 1 + asked_non_tried: List[List[Any]] = [] + while i < 100 and len(asked_non_tried) < n_points: + if i < 3: + asked = self.opt.ask(n_points=n_points) else: - break - return asked_non_tried[:n_points] + asked = self.opt.space.rvs(n_samples=n_points * 5) + asked_non_tried += [x for x in asked + if x not in self.opt.Xi + and x not in asked_non_tried] + i += 1 + if asked_non_tried: + return asked_non_tried[:n_points] + else: + return self.opt.ask(n_points=n_points) def start(self) -> None: self.random_state = self._set_random_state(self.config.get('hyperopt_random_state', None)) From 37a43019d6427952bfabd17b838590155fde14f1 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Mon, 21 Mar 2022 11:36:53 +0000 Subject: [PATCH 18/32] fix - clear cache before calling `ask` - avoid errors in case asked_non_tried has less than n_points elements --- freqtrade/optimize/hyperopt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index c1adbf45e..fe587a702 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -426,6 +426,7 @@ class Hyperopt: asked_non_tried: List[List[Any]] = [] while i < 100 and len(asked_non_tried) < n_points: if i < 3: + self.opt.cache_ = {} asked = self.opt.ask(n_points=n_points) else: asked = self.opt.space.rvs(n_samples=n_points * 5) @@ -434,7 +435,7 @@ class Hyperopt: and x not in asked_non_tried] i += 1 if asked_non_tried: - return asked_non_tried[:n_points] + return asked_non_tried[:min(len(asked_non_tried), n_points)] else: return self.opt.ask(n_points=n_points) From 2733aa33b6661523f721bd039cb8fd9e2ccdc7f1 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Tue, 22 Mar 2022 00:28:11 +0000 Subject: [PATCH 19/32] Update hyperopt.py --- freqtrade/optimize/hyperopt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index bbdc8bf27..f08fa7233 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -430,6 +430,7 @@ class Hyperopt: asked_non_tried: List[List[Any]] = [] while i < 100 and len(asked_non_tried) < n_points: if i < 3: + self.opt.cache_ = {} asked = self.opt.ask(n_points=n_points) else: asked = self.opt.space.rvs(n_samples=n_points * 5) @@ -438,7 +439,7 @@ class Hyperopt: and x not in asked_non_tried] i += 1 if asked_non_tried: - return asked_non_tried[:n_points] + return asked_non_tried[:min(len(asked_non_tried), n_points)] else: return self.opt.ask(n_points=n_points) From b5a346a46de13e7aefcd6be27ac354e701322b6d Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Tue, 22 Mar 2022 11:01:38 +0000 Subject: [PATCH 20/32] Update hyperopt.py --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index f08fa7233..d3f6a72f2 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -431,7 +431,7 @@ class Hyperopt: while i < 100 and len(asked_non_tried) < n_points: if i < 3: self.opt.cache_ = {} - asked = self.opt.ask(n_points=n_points) + asked = self.opt.ask(n_points=n_points * 5) else: asked = self.opt.space.rvs(n_samples=n_points * 5) asked_non_tried += [x for x in asked From 229b0b037eb3440f9fc93bd5d809f05eb5506ff4 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Tue, 29 Mar 2022 19:33:35 +0100 Subject: [PATCH 21/32] reduce search loops --- freqtrade/optimize/hyperopt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index fe587a702..4fad76570 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -424,10 +424,10 @@ class Hyperopt: ''' i = 0 asked_non_tried: List[List[Any]] = [] - while i < 100 and len(asked_non_tried) < n_points: + while i < 5 and len(asked_non_tried) < n_points: if i < 3: self.opt.cache_ = {} - asked = self.opt.ask(n_points=n_points) + asked = self.opt.ask(n_points=n_points * 5) else: asked = self.opt.space.rvs(n_samples=n_points * 5) asked_non_tried += [x for x in asked From a3b401a762bbdda75191956ea251d097c08e7a32 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Wed, 30 Mar 2022 00:29:14 +0100 Subject: [PATCH 22/32] highlight random points in hyperopt results table --- freqtrade/optimize/hyperopt.py | 21 ++++++++++++++++----- freqtrade/optimize/hyperopt_tools.py | 12 +++++++----- tests/conftest.py | 13 ++++++++++++- tests/optimize/test_hyperopt.py | 2 ++ 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 4fad76570..35f382469 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -10,7 +10,7 @@ import warnings from datetime import datetime, timezone from math import ceil from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Tuple import progressbar import rapidjson @@ -410,7 +410,7 @@ class Hyperopt: # Store non-trimmed data - will be trimmed after signal generation. dump(preprocessed, self.data_pickle_file) - def get_asked_points(self, n_points: int) -> List[List[Any]]: + def get_asked_points(self, n_points: int) -> Tuple[List[List[Any]], List[bool]]: ''' Enforce points returned from `self.opt.ask` have not been already evaluated @@ -424,20 +424,30 @@ class Hyperopt: ''' i = 0 asked_non_tried: List[List[Any]] = [] + is_random: List[bool] = [] while i < 5 and len(asked_non_tried) < n_points: if i < 3: self.opt.cache_ = {} asked = self.opt.ask(n_points=n_points * 5) + is_random = [False for _ in range(len(asked))] else: asked = self.opt.space.rvs(n_samples=n_points * 5) + is_random = [True for _ in range(len(asked))] asked_non_tried += [x for x in asked if x not in self.opt.Xi and x not in asked_non_tried] + is_random += [rand for x, rand in zip(asked, is_random) + if x not in self.opt.Xi + and x not in asked_non_tried] i += 1 + if asked_non_tried: - return asked_non_tried[:min(len(asked_non_tried), n_points)] + return ( + asked_non_tried[:min(len(asked_non_tried), n_points)], + is_random[:min(len(asked_non_tried), n_points)] + ) else: - return self.opt.ask(n_points=n_points) + return self.opt.ask(n_points=n_points), [False for _ in range(n_points)] def start(self) -> None: self.random_state = self._set_random_state(self.config.get('hyperopt_random_state', None)) @@ -503,7 +513,7 @@ class Hyperopt: n_rest = (i + 1) * jobs - self.total_epochs current_jobs = jobs - n_rest if n_rest > 0 else jobs - asked = self.get_asked_points(n_points=current_jobs) + asked, is_random = self.get_asked_points(n_points=current_jobs) f_val = self.run_optimizer_parallel(parallel, asked, i) self.opt.tell(asked, [v['loss'] for v in f_val]) @@ -522,6 +532,7 @@ class Hyperopt: # evaluations can take different time. Here they are aligned in the # order they will be shown to the user. val['is_best'] = is_best + val['is_random'] = is_random[j] self.print_results(val) if is_best: diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 8c84f772a..83df7e83c 100755 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -322,12 +322,12 @@ class HyperoptTools(): 'results_metrics.profit_total', 'results_metrics.holding_avg', 'results_metrics.max_drawdown', 'results_metrics.max_drawdown_account', 'results_metrics.max_drawdown_abs', - 'loss', 'is_initial_point', 'is_best']] + 'loss', 'is_initial_point', 'is_random', 'is_best']] trials.columns = [ 'Best', 'Epoch', 'Trades', ' Win Draw Loss', 'Avg profit', 'Total profit', 'Profit', 'Avg duration', 'max_drawdown', 'max_drawdown_account', - 'max_drawdown_abs', 'Objective', 'is_initial_point', 'is_best' + 'max_drawdown_abs', 'Objective', 'is_initial_point', 'is_random', 'is_best' ] return trials @@ -349,9 +349,11 @@ class HyperoptTools(): trials = HyperoptTools.prepare_trials_columns(trials, has_account_drawdown) trials['is_profit'] = False - trials.loc[trials['is_initial_point'], 'Best'] = '* ' + trials.loc[trials['is_initial_point'] | trials['is_random'], 'Best'] = '* ' trials.loc[trials['is_best'], 'Best'] = 'Best' - trials.loc[trials['is_initial_point'] & trials['is_best'], 'Best'] = '* Best' + trials.loc[ + (trials['is_initial_point'] | trials['is_random']) & trials['is_best'], + 'Best'] = '* Best' trials.loc[trials['Total profit'] > 0, 'is_profit'] = True trials['Trades'] = trials['Trades'].astype(str) # perc_multi = 1 if legacy_mode else 100 @@ -407,7 +409,7 @@ class HyperoptTools(): trials.iat[i, j] = "{}{}{}".format(Style.BRIGHT, str(trials.loc[i][j]), Style.RESET_ALL) - trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit']) + trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit', 'is_random']) if remove_header > 0: table = tabulate.tabulate( trials.to_dict(orient='list'), tablefmt='orgtbl', diff --git a/tests/conftest.py b/tests/conftest.py index 57122c01c..1dd6e8869 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2053,6 +2053,7 @@ def saved_hyperopt_results(): 'total_profit': -0.00125625, 'current_epoch': 1, 'is_initial_point': True, + 'is_random': False, 'is_best': True, }, { @@ -2069,6 +2070,7 @@ def saved_hyperopt_results(): 'total_profit': 6.185e-05, 'current_epoch': 2, 'is_initial_point': True, + 'is_random': False, 'is_best': False }, { 'loss': 14.241196856510731, @@ -2079,6 +2081,7 @@ def saved_hyperopt_results(): 'total_profit': -0.13639474, 'current_epoch': 3, 'is_initial_point': True, + 'is_random': False, 'is_best': False }, { 'loss': 100000, @@ -2086,7 +2089,7 @@ def saved_hyperopt_results(): 'params_details': {'buy': {'mfi-value': 13, 'fastd-value': 35, 'adx-value': 39, 'rsi-value': 29, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 54, 'sell-adx-value': 63, 'sell-rsi-value': 93, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.411946348378729, 215: 0.2052334363683207, 891: 0.06264755784937427, 2293: 0}, 'stoploss': {'stoploss': -0.11818343570194478}}, # noqa: E501 'results_metrics': {'total_trades': 0, 'wins': 0, 'draws': 0, 'losses': 0, 'profit_mean': None, 'profit_median': None, 'profit_total': 0, 'profit': 0.0, 'holding_avg': timedelta()}, # noqa: E501 'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.', # noqa: E501 - 'total_profit': 0, 'current_epoch': 4, 'is_initial_point': True, 'is_best': False + 'total_profit': 0, 'current_epoch': 4, 'is_initial_point': True, 'is_random': False, 'is_best': False }, { 'loss': 0.22195522184191518, 'params_dict': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 1269, 'roi_t2': 601, 'roi_t3': 444, 'roi_p1': 0.07280999507931168, 'roi_p2': 0.08946698095898986, 'roi_p3': 0.1454876733325284, 'stoploss': -0.18181041180901014}, # noqa: E501 @@ -2096,6 +2099,7 @@ def saved_hyperopt_results(): 'total_profit': -0.002480140000000001, 'current_epoch': 5, 'is_initial_point': True, + 'is_random': False, 'is_best': True }, { 'loss': 0.545315889154162, @@ -2106,6 +2110,7 @@ def saved_hyperopt_results(): 'total_profit': -0.0041773, 'current_epoch': 6, 'is_initial_point': True, + 'is_random': False, 'is_best': False }, { 'loss': 4.713497421432944, @@ -2118,6 +2123,7 @@ def saved_hyperopt_results(): 'total_profit': -0.06339929, 'current_epoch': 7, 'is_initial_point': True, + 'is_random': False, 'is_best': False }, { 'loss': 20.0, # noqa: E501 @@ -2128,6 +2134,7 @@ def saved_hyperopt_results(): 'total_profit': 0.0, 'current_epoch': 8, 'is_initial_point': True, + 'is_random': False, 'is_best': False }, { 'loss': 2.4731817780991223, @@ -2138,6 +2145,7 @@ def saved_hyperopt_results(): 'total_profit': -0.044050070000000004, # noqa: E501 'current_epoch': 9, 'is_initial_point': True, + 'is_random': False, 'is_best': False }, { 'loss': -0.2604606005845212, # noqa: E501 @@ -2148,6 +2156,7 @@ def saved_hyperopt_results(): 'total_profit': 0.00021629, 'current_epoch': 10, 'is_initial_point': True, + 'is_random': False, 'is_best': True }, { 'loss': 4.876465945994304, # noqa: E501 @@ -2159,6 +2168,7 @@ def saved_hyperopt_results(): 'total_profit': -0.07436117, 'current_epoch': 11, 'is_initial_point': True, + 'is_random': False, 'is_best': False }, { 'loss': 100000, @@ -2169,6 +2179,7 @@ def saved_hyperopt_results(): 'total_profit': 0, 'current_epoch': 12, 'is_initial_point': True, + 'is_random': False, 'is_best': False } ] diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index cc551277a..ef32e2466 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -41,6 +41,7 @@ def generate_result_metrics(): 'max_drawdown_abs': 0.001, 'loss': 0.001, 'is_initial_point': 0.001, + 'is_random': False, 'is_best': 1, } @@ -247,6 +248,7 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None: 'total_profit': 0, 'current_epoch': 2, # This starts from 1 (in a human-friendly manner) 'is_initial_point': False, + 'is_random': False, 'is_best': True } ) From 9f171193ef2b893b7f7c9269b9a2c6b796f0d71c Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Wed, 30 Mar 2022 09:39:07 +0100 Subject: [PATCH 23/32] Revert "Merge branch 'plot_hyperopt_stats' into opt-ask-force-new-points" This reverts commit 4eb9cc6e8b7bc99b543c4acbfa6c2b07f67d54e5, reversing changes made to a3b401a762bbdda75191956ea251d097c08e7a32. --- .github/workflows/ci.yml | 10 +-- .github/workflows/docker_update_readme.yml | 2 +- docs/includes/pricing.md | 8 +- docs/requirements-docs.txt | 7 +- docs/strategy-advanced.md | 17 ++-- freqtrade/__init__.py | 17 +++- freqtrade/optimize/hyperopt.py | 89 +------------------ freqtrade/resolvers/iresolver.py | 60 ++++--------- requirements-dev.txt | 12 +-- requirements-hyperopt.txt | 1 - requirements.txt | 8 +- tests/conftest.py | 30 +++---- tests/exchange/test_exchange.py | 2 +- .../strategy/strats/hyperoptable_strategy.py | 88 +++++++++++++++++- tests/strategy/strats/strategy_test_v2.py | 2 +- 15 files changed, 169 insertions(+), 184 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8df7ab10..216a53bc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,14 +31,14 @@ jobs: python-version: ${{ matrix.python-version }} - name: Cache_dependencies - uses: actions/cache@v3 + uses: actions/cache@v2 id: cache with: path: ~/dependencies/ key: ${{ runner.os }}-dependencies - name: pip cache (linux) - uses: actions/cache@v3 + uses: actions/cache@v2 if: runner.os == 'Linux' with: path: ~/.cache/pip @@ -126,14 +126,14 @@ jobs: python-version: ${{ matrix.python-version }} - name: Cache_dependencies - uses: actions/cache@v3 + uses: actions/cache@v2 id: cache with: path: ~/dependencies/ key: ${{ runner.os }}-dependencies - name: pip cache (macOS) - uses: actions/cache@v3 + uses: actions/cache@v2 if: runner.os == 'macOS' with: path: ~/Library/Caches/pip @@ -218,7 +218,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Pip cache (Windows) - uses: actions/cache@v3 + uses: actions/cache@preview with: path: ~\AppData\Local\pip\Cache key: ${{ matrix.os }}-${{ matrix.python-version }}-pip diff --git a/.github/workflows/docker_update_readme.yml b/.github/workflows/docker_update_readme.yml index 822533ee2..ebb773ad7 100644 --- a/.github/workflows/docker_update_readme.yml +++ b/.github/workflows/docker_update_readme.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Docker Hub Description - uses: peter-evans/dockerhub-description@v3 + uses: peter-evans/dockerhub-description@v2.4.3 env: DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} diff --git a/docs/includes/pricing.md b/docs/includes/pricing.md index 103df6cd3..ed8a45e68 100644 --- a/docs/includes/pricing.md +++ b/docs/includes/pricing.md @@ -51,9 +51,9 @@ When buying with the orderbook enabled (`bid_strategy.use_order_book=True`), Fre #### Buy price without Orderbook enabled -The following section uses `side` as the configured `bid_strategy.price_side` (defaults to `"bid"`). +The following section uses `side` as the configured `bid_strategy.price_side`. -When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price based on `bid_strategy.ask_last_balance`.. +When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price. The `bid_strategy.ask_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price. @@ -88,9 +88,9 @@ When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Fr #### Sell price without Orderbook enabled -The following section uses `side` as the configured `ask_strategy.price_side` (defaults to `"ask"`). +When not using orderbook (`ask_strategy.use_order_book=False`), the price at the `ask_strategy.price_side` side (defaults to `"ask"`) from the ticker will be used as the sell price. -When not using orderbook (`ask_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's above the `last` traded price from the ticker. Otherwise (when the `side` price is below the `last` price), it calculates a rate between `side` and `last` price based on `ask_strategy.bid_last_balance`. +When not using orderbook (`ask_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price. The `ask_strategy.bid_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the last price and values between those interpolate between `side` and last price. diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 1f7db75c5..0ca0e4b63 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,5 +1,4 @@ -mkdocs==1.3.0 -mkdocs-material==8.2.8 +mkdocs==1.2.3 +mkdocs-material==8.2.5 mdx_truly_sane_lists==1.2 -pymdown-extensions==9.3 -jinja2==3.1.1 +pymdown-extensions==9.2 diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index b1f154355..3793abacf 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -146,7 +146,7 @@ def version(self) -> str: The strategies can be derived from other strategies. This avoids duplication of your custom strategy code. You can use this technique to override small parts of your main strategy, leaving the rest untouched: -``` python title="user_data/strategies/myawesomestrategy.py" +``` python class MyAwesomeStrategy(IStrategy): ... stoploss = 0.13 @@ -155,10 +155,6 @@ class MyAwesomeStrategy(IStrategy): # should be in any custom strategy... ... -``` - -``` python title="user_data/strategies/MyAwesomeStrategy2.py" -from myawesomestrategy import MyAwesomeStrategy class MyAwesomeStrategy2(MyAwesomeStrategy): # Override something stoploss = 0.08 @@ -167,7 +163,16 @@ class MyAwesomeStrategy2(MyAwesomeStrategy): Both attributes and methods may be overridden, altering behavior of the original strategy in a way you need. -While keeping the subclass in the same file is technically possible, it can lead to some problems with hyperopt parameter files, we therefore recommend to use separate strategy files, and import the parent strategy as shown above. +!!! Note "Parent-strategy in different files" + If you have the parent-strategy in a different file, you'll need to add the following to the top of your "child"-file to ensure proper loading, otherwise freqtrade may not be able to load the parent strategy correctly. + + ``` python + import sys + from pathlib import Path + sys.path.append(str(Path(__file__).parent)) + + from myawesomestrategy import MyAwesomeStrategy + ``` ## Embedding Strategies diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index f8be8f66f..2747efc96 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,14 +1,27 @@ """ Freqtrade bot """ __version__ = 'develop' -if 'dev' in __version__: +if __version__ == 'develop': + try: import subprocess - __version__ = __version__ + '-' + subprocess.check_output( + __version__ = 'develop-' + subprocess.check_output( ['git', 'log', '--format="%h"', '-n 1'], stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"') + # from datetime import datetime + # last_release = subprocess.check_output( + # ['git', 'tag'] + # ).decode('utf-8').split()[-1].split(".") + # # Releases are in the format "2020.1" - we increment the latest version for dev. + # prefix = f"{last_release[0]}.{int(last_release[1]) + 1}" + # dev_version = int(datetime.now().timestamp() // 1000) + # __version__ = f"{prefix}.dev{dev_version}" + + # subprocess.check_output( + # ['git', 'log', '--format="%h"', '-n 1'], + # stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"') except Exception: # pragma: no cover # git not available, ignore try: diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 223673113..35f382469 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -32,24 +32,20 @@ from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F4 from freqtrade.optimize.hyperopt_tools import HyperoptTools, hyperopt_serializer from freqtrade.optimize.optimize_reports import generate_strategy_stats from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver -import matplotlib.pyplot as plt -import numpy as np -import random + # Suppress scikit-learn FutureWarnings from skopt with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=FutureWarning) from skopt import Optimizer from skopt.space import Dimension - from sklearn.model_selection import cross_val_score - from skopt.plots import plot_convergence, plot_regret, plot_evaluations, plot_objective progressbar.streams.wrap_stderr() progressbar.streams.wrap_stdout() logger = logging.getLogger(__name__) -INITIAL_POINTS = 32 +INITIAL_POINTS = 30 # Keep no more than SKOPT_MODEL_QUEUE_SIZE models # in the skopt model queue, to optimize memory consumption @@ -413,35 +409,6 @@ class Hyperopt: f'({(self.max_date - self.min_date).days} days)..') # Store non-trimmed data - will be trimmed after signal generation. dump(preprocessed, self.data_pickle_file) - - def get_asked_points(self, n_points: int) -> List[List[Any]]: - ''' - Enforce points returned from `self.opt.ask` have not been already evaluated - - Steps: - 1. Try to get points using `self.opt.ask` first - 2. Discard the points that have already been evaluated - 3. Retry using `self.opt.ask` up to 3 times - 4. If still some points are missing in respect to `n_points`, random sample some points - 5. Repeat until at least `n_points` points in the `asked_non_tried` list - 6. Return a list with length truncated at `n_points` - ''' - i = 0 - asked_non_tried: List[List[Any]] = [] - while i < 100 and len(asked_non_tried) < n_points: - if i < 3: - self.opt.cache_ = {} - asked = self.opt.ask(n_points=n_points * 5) - else: - asked = self.opt.space.rvs(n_samples=n_points * 5) - asked_non_tried += [x for x in asked - if x not in self.opt.Xi - and x not in asked_non_tried] - i += 1 - if asked_non_tried: - return asked_non_tried[:min(len(asked_non_tried), n_points)] - else: - return self.opt.ask(n_points=n_points) def get_asked_points(self, n_points: int) -> Tuple[List[List[Any]], List[bool]]: ''' @@ -548,13 +515,7 @@ class Hyperopt: asked, is_random = self.get_asked_points(n_points=current_jobs) f_val = self.run_optimizer_parallel(parallel, asked, i) - res = self.opt.tell(asked, [v['loss'] for v in f_val]) - - self.plot_optimizer(res, path='user_data/scripts', convergence=False, regret=False, r2=False, objective=True, jobs=jobs) - - if res.models and hasattr(res.models[-1], "kernel_"): - print(f'kernel: {res.models[-1].kernel_}') - print(datetime.now()) + self.opt.tell(asked, [v['loss'] for v in f_val]) # Calculate progressbar outputs for j, val in enumerate(f_val): @@ -600,47 +561,3 @@ class Hyperopt: # This is printed when Ctrl+C is pressed quickly, before first epochs have # a chance to be evaluated. print("No epochs evaluated yet, no best result.") - - def plot_r2(self, res, ax, jobs): - if len(res.x_iters) < 10: - return - - if not hasattr(self, 'r2_list'): - self.r2_list = [] - - model = res.models[-1] - model.criterion = 'squared_error' - - r2 = cross_val_score(model, X=res.x_iters, y=res.func_vals, scoring='r2', cv=5, n_jobs=jobs).mean() - r2 = r2 if r2 > -5 else -5 - self.r2_list.append(r2) - - ax.plot(range(INITIAL_POINTS, INITIAL_POINTS + jobs * len(self.r2_list), jobs), self.r2_list, label='R2', marker=".", markersize=12, lw=2) - - def plot_optimizer(self, res, path, jobs, convergence=True, regret=True, evaluations=True, objective=True, r2=True): - path = Path(path) - if convergence: - ax = plot_convergence(res) - ax.flatten()[0].figure.savefig(path / 'convergence.png') - - if regret: - ax = plot_regret(res) - ax.flatten()[0].figure.savefig(path / 'regret.png') - - if evaluations: -# print('evaluations') - ax = plot_evaluations(res) - ax.flatten()[0].figure.savefig(path / 'evaluations.png') - - if objective and res.models: -# print('objective') - ax = plot_objective(res, sample_source='result', n_samples=50, n_points=10) - ax.flatten()[0].figure.savefig(path / 'objective.png') - - if r2 and res.models: - fig, ax = plt.subplots() - ax.set_ylabel('R2') - ax.set_xlabel('Epoch') - ax.set_title('R2') - ax = self.plot_r2(res, ax, jobs) - fig.savefig(path / 'r2.png') diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 3ab461041..c6f97c976 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -6,7 +6,6 @@ This module load custom objects import importlib.util import inspect import logging -import sys from pathlib import Path from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union @@ -16,22 +15,6 @@ from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) -class PathModifier: - def __init__(self, path: Path): - self.path = path - - def __enter__(self): - """Inject path to allow importing with relative imports.""" - sys.path.insert(0, str(self.path)) - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - """Undo insertion of local path.""" - str_path = str(self.path) - if str_path in sys.path: - sys.path.remove(str_path) - - class IResolver: """ This class contains all the logic to load custom classes @@ -74,32 +57,27 @@ class IResolver: # Generate spec based on absolute path # Pass object_name as first argument to have logging print a reasonable name. - with PathModifier(module_path.parent): - module_name = module_path.stem or "" - spec = importlib.util.spec_from_file_location(module_name, str(module_path)) - if not spec: + spec = importlib.util.spec_from_file_location(object_name or "", str(module_path)) + if not spec: + return iter([None]) + + module = importlib.util.module_from_spec(spec) + try: + spec.loader.exec_module(module) # type: ignore # importlib does not use typehints + except (ModuleNotFoundError, SyntaxError, ImportError, NameError) as err: + # Catch errors in case a specific module is not installed + logger.warning(f"Could not import {module_path} due to '{err}'") + if enum_failed: return iter([None]) - module = importlib.util.module_from_spec(spec) - try: - spec.loader.exec_module(module) # type: ignore # importlib does not use typehints - except (ModuleNotFoundError, SyntaxError, ImportError, NameError) as err: - # Catch errors in case a specific module is not installed - logger.warning(f"Could not import {module_path} due to '{err}'") - if enum_failed: - return iter([None]) - - valid_objects_gen = ( - (obj, inspect.getsource(module)) for - name, obj in inspect.getmembers( - module, inspect.isclass) if ((object_name is None or object_name == name) - and issubclass(obj, cls.object_type) - and obj is not cls.object_type - and obj.__module__ == module_name - ) - ) - # The __module__ check ensures we only use strategies that are defined in this folder. - return valid_objects_gen + valid_objects_gen = ( + (obj, inspect.getsource(module)) for + name, obj in inspect.getmembers( + module, inspect.isclass) if ((object_name is None or object_name == name) + and issubclass(obj, cls.object_type) + and obj is not cls.object_type) + ) + return valid_objects_gen @classmethod def _search_object(cls, directory: Path, *, object_name: str, add_source: bool = False diff --git a/requirements-dev.txt b/requirements-dev.txt index 063cfaa45..c2f3eae8a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,9 +6,9 @@ coveralls==3.3.1 flake8==4.0.1 flake8-tidy-imports==4.6.0 -mypy==0.942 -pytest==7.1.1 -pytest-asyncio==0.18.3 +mypy==0.940 +pytest==7.1.0 +pytest-asyncio==0.18.2 pytest-cov==3.0.0 pytest-mock==3.7.0 pytest-random-order==1.0.4 @@ -22,8 +22,8 @@ nbconvert==6.4.4 # mypy types types-cachetools==5.0.0 types-filelock==3.2.5 -types-requests==2.27.15 -types-tabulate==0.8.6 +types-requests==2.27.12 +types-tabulate==0.8.5 # Extensions to datetime library -types-python-dateutil==2.8.10 \ No newline at end of file +types-python-dateutil==2.8.9 \ No newline at end of file diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index ad85ac71a..aeb7be035 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -8,4 +8,3 @@ scikit-optimize==0.9.0 filelock==3.6.0 joblib==1.1.0 progressbar2==4.0.0 -matplotlib \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index de05b3f7c..f0f030e78 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,22 +2,22 @@ numpy==1.22.3 pandas==1.4.1 pandas-ta==0.3.14b -ccxt==1.77.36 +ccxt==1.76.5 # Pin cryptography for now due to rust build errors with piwheels -cryptography==36.0.2 +cryptography==36.0.1 aiohttp==3.8.1 SQLAlchemy==1.4.32 python-telegram-bot==13.11 arrow==1.2.2 cachetools==4.2.2 requests==2.27.1 -urllib3==1.26.9 +urllib3==1.26.8 jsonschema==4.4.0 TA-Lib==0.4.24 technical==1.3.0 tabulate==0.8.9 pycoingecko==2.2.0 -jinja2==3.1.1 +jinja2==3.0.3 tables==3.7.0 blosc==1.10.6 diff --git a/tests/conftest.py b/tests/conftest.py index 809342c03..1dd6e8869 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1019,8 +1019,8 @@ def limit_buy_order_open(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().int_timestamp * 1000, 'datetime': arrow.utcnow().isoformat(), + 'timestamp': arrow.utcnow().int_timestamp, 'price': 0.00001099, 'amount': 90.99181073, 'filled': 0.0, @@ -1046,7 +1046,6 @@ def market_buy_order(): 'type': 'market', 'side': 'buy', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().int_timestamp * 1000, 'datetime': arrow.utcnow().isoformat(), 'price': 0.00004099, 'amount': 91.99181073, @@ -1063,7 +1062,6 @@ def market_sell_order(): 'type': 'market', 'side': 'sell', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().int_timestamp * 1000, 'datetime': arrow.utcnow().isoformat(), 'price': 0.00004173, 'amount': 91.99181073, @@ -1080,8 +1078,7 @@ def limit_buy_order_old(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', - 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, + 'datetime': str(arrow.utcnow().shift(minutes=-601).datetime), 'price': 0.00001099, 'amount': 90.99181073, 'filled': 0.0, @@ -1097,7 +1094,6 @@ def limit_sell_order_old(): 'type': 'limit', 'side': 'sell', 'symbol': 'ETH/BTC', - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'price': 0.00001099, 'amount': 90.99181073, @@ -1114,7 +1110,6 @@ def limit_buy_order_old_partial(): 'type': 'limit', 'side': 'buy', 'symbol': 'ETH/BTC', - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'price': 0.00001099, 'amount': 90.99181073, @@ -1144,7 +1139,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': '1234512345', 'clientOrderId': None, - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', @@ -1165,7 +1160,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': 'AZNPFF-4AC4N-7MKTAT', 'clientOrderId': None, - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'status': 'canceled', @@ -1186,7 +1181,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': '1234512345', 'clientOrderId': 'alb1234123', - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', @@ -1207,7 +1202,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': '1234512345', 'clientOrderId': 'alb1234123', - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', @@ -1233,7 +1228,7 @@ def limit_sell_order_open(): 'side': 'sell', 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), - 'timestamp': arrow.utcnow().int_timestamp * 1000, + 'timestamp': arrow.utcnow().int_timestamp, 'price': 0.00001173, 'amount': 90.99181073, 'filled': 0.0, @@ -1399,7 +1394,7 @@ def tickers(): 'BLK/BTC': { 'symbol': 'BLK/BTC', 'timestamp': 1522014806072, - 'datetime': '2018-03-25T21:53:26.072Z', + 'datetime': '2018-03-25T21:53:26.720Z', 'high': 0.007745, 'low': 0.007512, 'bid': 0.007729, @@ -1895,8 +1890,7 @@ def buy_order_fee(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, - 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), + 'datetime': str(arrow.utcnow().shift(minutes=-601).datetime), 'price': 0.245441, 'amount': 8.0, 'cost': 1.963528, @@ -2205,7 +2199,7 @@ def limit_buy_order_usdt_open(): 'side': 'buy', 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), - 'timestamp': arrow.utcnow().int_timestamp * 1000, + 'timestamp': arrow.utcnow().int_timestamp, 'price': 2.00, 'amount': 30.0, 'filled': 0.0, @@ -2232,7 +2226,7 @@ def limit_sell_order_usdt_open(): 'side': 'sell', 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), - 'timestamp': arrow.utcnow().int_timestamp * 1000, + 'timestamp': arrow.utcnow().int_timestamp, 'price': 2.20, 'amount': 30.0, 'filled': 0.0, @@ -2257,7 +2251,6 @@ def market_buy_order_usdt(): 'type': 'market', 'side': 'buy', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().int_timestamp * 1000, 'datetime': arrow.utcnow().isoformat(), 'price': 2.00, 'amount': 30.0, @@ -2314,7 +2307,6 @@ def market_sell_order_usdt(): 'type': 'market', 'side': 'sell', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().int_timestamp * 1000, 'datetime': arrow.utcnow().isoformat(), 'price': 2.20, 'amount': 30.0, diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index b76cb23e6..ff8383997 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1098,7 +1098,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.create_order( - pair='ETH/BTC', ordertype=ordertype, side=side, amount=1, rate=rate) + pair='ETH/BTC', ordertype=ordertype, side=side, amount=1, rate=200) assert 'id' in order assert 'info' in order diff --git a/tests/strategy/strats/hyperoptable_strategy.py b/tests/strategy/strats/hyperoptable_strategy.py index dc6b03a3e..88bdd078e 100644 --- a/tests/strategy/strats/hyperoptable_strategy.py +++ b/tests/strategy/strats/hyperoptable_strategy.py @@ -1,13 +1,14 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement +import talib.abstract as ta from pandas import DataFrame -from strategy_test_v2 import StrategyTestV2 import freqtrade.vendor.qtpylib.indicators as qtpylib -from freqtrade.strategy import BooleanParameter, DecimalParameter, IntParameter, RealParameter +from freqtrade.strategy import (BooleanParameter, DecimalParameter, IntParameter, IStrategy, + RealParameter) -class HyperoptableStrategy(StrategyTestV2): +class HyperoptableStrategy(IStrategy): """ Default Strategy provided by freqtrade bot. Please do not modify this strategy, it's intended for internal use only. @@ -15,6 +16,38 @@ class HyperoptableStrategy(StrategyTestV2): or strategy repository https://github.com/freqtrade/freqtrade-strategies for samples and inspiration. """ + INTERFACE_VERSION = 2 + + # Minimal ROI designed for the strategy + minimal_roi = { + "40": 0.0, + "30": 0.01, + "20": 0.02, + "0": 0.04 + } + + # Optimal stoploss designed for the strategy + stoploss = -0.10 + + # Optimal ticker interval for the strategy + timeframe = '5m' + + # Optional order type mapping + order_types = { + 'buy': 'limit', + 'sell': 'limit', + 'stoploss': 'limit', + 'stoploss_on_exchange': False + } + + # Number of candles the strategy requires before producing valid signals + startup_candle_count: int = 20 + + # Optional time in force for orders + order_time_in_force = { + 'buy': 'gtc', + 'sell': 'gtc', + } buy_params = { 'buy_rsi': 35, @@ -58,6 +91,55 @@ class HyperoptableStrategy(StrategyTestV2): """ return [] + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Adds several different TA indicators to the given DataFrame + + Performance Note: For the best performance be frugal on the number of indicators + you are using. Let uncomment only the indicator you are using in your strategies + or your hyperopt configuration, otherwise you will waste your memory and CPU usage. + :param dataframe: Dataframe with data from the exchange + :param metadata: Additional information, like the currently traded pair + :return: a Dataframe with all mandatory indicators for the strategies + """ + + # Momentum Indicator + # ------------------------------------ + + # ADX + dataframe['adx'] = ta.ADX(dataframe) + + # MACD + macd = ta.MACD(dataframe) + dataframe['macd'] = macd['macd'] + dataframe['macdsignal'] = macd['macdsignal'] + dataframe['macdhist'] = macd['macdhist'] + + # Minus Directional Indicator / Movement + dataframe['minus_di'] = ta.MINUS_DI(dataframe) + + # Plus Directional Indicator / Movement + dataframe['plus_di'] = ta.PLUS_DI(dataframe) + + # RSI + dataframe['rsi'] = ta.RSI(dataframe) + + # Stoch fast + stoch_fast = ta.STOCHF(dataframe) + dataframe['fastd'] = stoch_fast['fastd'] + dataframe['fastk'] = stoch_fast['fastk'] + + # Bollinger bands + bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) + dataframe['bb_lowerband'] = bollinger['lower'] + dataframe['bb_middleband'] = bollinger['mid'] + dataframe['bb_upperband'] = bollinger['upper'] + + # EMA - Exponential Moving Average + dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) + + return dataframe + def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe diff --git a/tests/strategy/strats/strategy_test_v2.py b/tests/strategy/strats/strategy_test_v2.py index 59f1f569e..c57becdad 100644 --- a/tests/strategy/strats/strategy_test_v2.py +++ b/tests/strategy/strats/strategy_test_v2.py @@ -7,7 +7,7 @@ from pandas import DataFrame import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.persistence import Trade -from freqtrade.strategy import IStrategy +from freqtrade.strategy.interface import IStrategy class StrategyTestV2(IStrategy): From 3e24d01af401dd1fbe11b9c3951a6e848f8af716 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Wed, 30 Mar 2022 09:41:40 +0100 Subject: [PATCH 24/32] fix flake8 --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 1dd6e8869..0387f0a22 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2089,7 +2089,7 @@ def saved_hyperopt_results(): 'params_details': {'buy': {'mfi-value': 13, 'fastd-value': 35, 'adx-value': 39, 'rsi-value': 29, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 54, 'sell-adx-value': 63, 'sell-rsi-value': 93, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.411946348378729, 215: 0.2052334363683207, 891: 0.06264755784937427, 2293: 0}, 'stoploss': {'stoploss': -0.11818343570194478}}, # noqa: E501 'results_metrics': {'total_trades': 0, 'wins': 0, 'draws': 0, 'losses': 0, 'profit_mean': None, 'profit_median': None, 'profit_total': 0, 'profit': 0.0, 'holding_avg': timedelta()}, # noqa: E501 'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.', # noqa: E501 - 'total_profit': 0, 'current_epoch': 4, 'is_initial_point': True, 'is_random': False, 'is_best': False + 'total_profit': 0, 'current_epoch': 4, 'is_initial_point': True, 'is_random': False, 'is_best': False # noqa: E501 }, { 'loss': 0.22195522184191518, 'params_dict': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 1269, 'roi_t2': 601, 'roi_t3': 444, 'roi_p1': 0.07280999507931168, 'roi_p2': 0.08946698095898986, 'roi_p3': 0.1454876733325284, 'stoploss': -0.18181041180901014}, # noqa: E501 From bad179ebaa605927e956e2bf6e7d8391538b9f31 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Wed, 30 Mar 2022 09:48:10 +0100 Subject: [PATCH 25/32] fix merge mess This reverts commit 9f171193ef2b893b7f7c9269b9a2c6b796f0d71c. --- .github/workflows/ci.yml | 10 +-- .github/workflows/docker_update_readme.yml | 2 +- docs/includes/pricing.md | 8 +- docs/requirements-docs.txt | 7 +- docs/strategy-advanced.md | 17 ++-- freqtrade/__init__.py | 17 +--- freqtrade/resolvers/iresolver.py | 60 +++++++++---- requirements-dev.txt | 12 +-- requirements.txt | 8 +- tests/conftest.py | 30 ++++--- tests/exchange/test_exchange.py | 2 +- .../strategy/strats/hyperoptable_strategy.py | 88 +------------------ tests/strategy/strats/strategy_test_v2.py | 2 +- 13 files changed, 97 insertions(+), 166 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 216a53bc1..b8df7ab10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,14 +31,14 @@ jobs: python-version: ${{ matrix.python-version }} - name: Cache_dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 id: cache with: path: ~/dependencies/ key: ${{ runner.os }}-dependencies - name: pip cache (linux) - uses: actions/cache@v2 + uses: actions/cache@v3 if: runner.os == 'Linux' with: path: ~/.cache/pip @@ -126,14 +126,14 @@ jobs: python-version: ${{ matrix.python-version }} - name: Cache_dependencies - uses: actions/cache@v2 + uses: actions/cache@v3 id: cache with: path: ~/dependencies/ key: ${{ runner.os }}-dependencies - name: pip cache (macOS) - uses: actions/cache@v2 + uses: actions/cache@v3 if: runner.os == 'macOS' with: path: ~/Library/Caches/pip @@ -218,7 +218,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Pip cache (Windows) - uses: actions/cache@preview + uses: actions/cache@v3 with: path: ~\AppData\Local\pip\Cache key: ${{ matrix.os }}-${{ matrix.python-version }}-pip diff --git a/.github/workflows/docker_update_readme.yml b/.github/workflows/docker_update_readme.yml index ebb773ad7..822533ee2 100644 --- a/.github/workflows/docker_update_readme.yml +++ b/.github/workflows/docker_update_readme.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Docker Hub Description - uses: peter-evans/dockerhub-description@v2.4.3 + uses: peter-evans/dockerhub-description@v3 env: DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} diff --git a/docs/includes/pricing.md b/docs/includes/pricing.md index ed8a45e68..103df6cd3 100644 --- a/docs/includes/pricing.md +++ b/docs/includes/pricing.md @@ -51,9 +51,9 @@ When buying with the orderbook enabled (`bid_strategy.use_order_book=True`), Fre #### Buy price without Orderbook enabled -The following section uses `side` as the configured `bid_strategy.price_side`. +The following section uses `side` as the configured `bid_strategy.price_side` (defaults to `"bid"`). -When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price. +When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price based on `bid_strategy.ask_last_balance`.. The `bid_strategy.ask_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price. @@ -88,9 +88,9 @@ When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Fr #### Sell price without Orderbook enabled -When not using orderbook (`ask_strategy.use_order_book=False`), the price at the `ask_strategy.price_side` side (defaults to `"ask"`) from the ticker will be used as the sell price. +The following section uses `side` as the configured `ask_strategy.price_side` (defaults to `"ask"`). -When not using orderbook (`ask_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price. +When not using orderbook (`ask_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's above the `last` traded price from the ticker. Otherwise (when the `side` price is below the `last` price), it calculates a rate between `side` and `last` price based on `ask_strategy.bid_last_balance`. The `ask_strategy.bid_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the last price and values between those interpolate between `side` and last price. diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 0ca0e4b63..1f7db75c5 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,4 +1,5 @@ -mkdocs==1.2.3 -mkdocs-material==8.2.5 +mkdocs==1.3.0 +mkdocs-material==8.2.8 mdx_truly_sane_lists==1.2 -pymdown-extensions==9.2 +pymdown-extensions==9.3 +jinja2==3.1.1 diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 3793abacf..b1f154355 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -146,7 +146,7 @@ def version(self) -> str: The strategies can be derived from other strategies. This avoids duplication of your custom strategy code. You can use this technique to override small parts of your main strategy, leaving the rest untouched: -``` python +``` python title="user_data/strategies/myawesomestrategy.py" class MyAwesomeStrategy(IStrategy): ... stoploss = 0.13 @@ -155,6 +155,10 @@ class MyAwesomeStrategy(IStrategy): # should be in any custom strategy... ... +``` + +``` python title="user_data/strategies/MyAwesomeStrategy2.py" +from myawesomestrategy import MyAwesomeStrategy class MyAwesomeStrategy2(MyAwesomeStrategy): # Override something stoploss = 0.08 @@ -163,16 +167,7 @@ class MyAwesomeStrategy2(MyAwesomeStrategy): Both attributes and methods may be overridden, altering behavior of the original strategy in a way you need. -!!! Note "Parent-strategy in different files" - If you have the parent-strategy in a different file, you'll need to add the following to the top of your "child"-file to ensure proper loading, otherwise freqtrade may not be able to load the parent strategy correctly. - - ``` python - import sys - from pathlib import Path - sys.path.append(str(Path(__file__).parent)) - - from myawesomestrategy import MyAwesomeStrategy - ``` +While keeping the subclass in the same file is technically possible, it can lead to some problems with hyperopt parameter files, we therefore recommend to use separate strategy files, and import the parent strategy as shown above. ## Embedding Strategies diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 2747efc96..f8be8f66f 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,27 +1,14 @@ """ Freqtrade bot """ __version__ = 'develop' -if __version__ == 'develop': - +if 'dev' in __version__: try: import subprocess - __version__ = 'develop-' + subprocess.check_output( + __version__ = __version__ + '-' + subprocess.check_output( ['git', 'log', '--format="%h"', '-n 1'], stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"') - # from datetime import datetime - # last_release = subprocess.check_output( - # ['git', 'tag'] - # ).decode('utf-8').split()[-1].split(".") - # # Releases are in the format "2020.1" - we increment the latest version for dev. - # prefix = f"{last_release[0]}.{int(last_release[1]) + 1}" - # dev_version = int(datetime.now().timestamp() // 1000) - # __version__ = f"{prefix}.dev{dev_version}" - - # subprocess.check_output( - # ['git', 'log', '--format="%h"', '-n 1'], - # stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"') except Exception: # pragma: no cover # git not available, ignore try: diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index c6f97c976..3ab461041 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -6,6 +6,7 @@ This module load custom objects import importlib.util import inspect import logging +import sys from pathlib import Path from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union @@ -15,6 +16,22 @@ from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) +class PathModifier: + def __init__(self, path: Path): + self.path = path + + def __enter__(self): + """Inject path to allow importing with relative imports.""" + sys.path.insert(0, str(self.path)) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Undo insertion of local path.""" + str_path = str(self.path) + if str_path in sys.path: + sys.path.remove(str_path) + + class IResolver: """ This class contains all the logic to load custom classes @@ -57,27 +74,32 @@ class IResolver: # Generate spec based on absolute path # Pass object_name as first argument to have logging print a reasonable name. - spec = importlib.util.spec_from_file_location(object_name or "", str(module_path)) - if not spec: - return iter([None]) - - module = importlib.util.module_from_spec(spec) - try: - spec.loader.exec_module(module) # type: ignore # importlib does not use typehints - except (ModuleNotFoundError, SyntaxError, ImportError, NameError) as err: - # Catch errors in case a specific module is not installed - logger.warning(f"Could not import {module_path} due to '{err}'") - if enum_failed: + with PathModifier(module_path.parent): + module_name = module_path.stem or "" + spec = importlib.util.spec_from_file_location(module_name, str(module_path)) + if not spec: return iter([None]) - valid_objects_gen = ( - (obj, inspect.getsource(module)) for - name, obj in inspect.getmembers( - module, inspect.isclass) if ((object_name is None or object_name == name) - and issubclass(obj, cls.object_type) - and obj is not cls.object_type) - ) - return valid_objects_gen + module = importlib.util.module_from_spec(spec) + try: + spec.loader.exec_module(module) # type: ignore # importlib does not use typehints + except (ModuleNotFoundError, SyntaxError, ImportError, NameError) as err: + # Catch errors in case a specific module is not installed + logger.warning(f"Could not import {module_path} due to '{err}'") + if enum_failed: + return iter([None]) + + valid_objects_gen = ( + (obj, inspect.getsource(module)) for + name, obj in inspect.getmembers( + module, inspect.isclass) if ((object_name is None or object_name == name) + and issubclass(obj, cls.object_type) + and obj is not cls.object_type + and obj.__module__ == module_name + ) + ) + # The __module__ check ensures we only use strategies that are defined in this folder. + return valid_objects_gen @classmethod def _search_object(cls, directory: Path, *, object_name: str, add_source: bool = False diff --git a/requirements-dev.txt b/requirements-dev.txt index c2f3eae8a..063cfaa45 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,9 +6,9 @@ coveralls==3.3.1 flake8==4.0.1 flake8-tidy-imports==4.6.0 -mypy==0.940 -pytest==7.1.0 -pytest-asyncio==0.18.2 +mypy==0.942 +pytest==7.1.1 +pytest-asyncio==0.18.3 pytest-cov==3.0.0 pytest-mock==3.7.0 pytest-random-order==1.0.4 @@ -22,8 +22,8 @@ nbconvert==6.4.4 # mypy types types-cachetools==5.0.0 types-filelock==3.2.5 -types-requests==2.27.12 -types-tabulate==0.8.5 +types-requests==2.27.15 +types-tabulate==0.8.6 # Extensions to datetime library -types-python-dateutil==2.8.9 \ No newline at end of file +types-python-dateutil==2.8.10 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f0f030e78..de05b3f7c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,22 +2,22 @@ numpy==1.22.3 pandas==1.4.1 pandas-ta==0.3.14b -ccxt==1.76.5 +ccxt==1.77.36 # Pin cryptography for now due to rust build errors with piwheels -cryptography==36.0.1 +cryptography==36.0.2 aiohttp==3.8.1 SQLAlchemy==1.4.32 python-telegram-bot==13.11 arrow==1.2.2 cachetools==4.2.2 requests==2.27.1 -urllib3==1.26.8 +urllib3==1.26.9 jsonschema==4.4.0 TA-Lib==0.4.24 technical==1.3.0 tabulate==0.8.9 pycoingecko==2.2.0 -jinja2==3.0.3 +jinja2==3.1.1 tables==3.7.0 blosc==1.10.6 diff --git a/tests/conftest.py b/tests/conftest.py index 0387f0a22..b15716fcc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1019,8 +1019,8 @@ def limit_buy_order_open(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'datetime': arrow.utcnow().isoformat(), - 'timestamp': arrow.utcnow().int_timestamp, 'price': 0.00001099, 'amount': 90.99181073, 'filled': 0.0, @@ -1046,6 +1046,7 @@ def market_buy_order(): 'type': 'market', 'side': 'buy', 'symbol': 'mocked', + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'datetime': arrow.utcnow().isoformat(), 'price': 0.00004099, 'amount': 91.99181073, @@ -1062,6 +1063,7 @@ def market_sell_order(): 'type': 'market', 'side': 'sell', 'symbol': 'mocked', + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'datetime': arrow.utcnow().isoformat(), 'price': 0.00004173, 'amount': 91.99181073, @@ -1078,7 +1080,8 @@ def limit_buy_order_old(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', - 'datetime': str(arrow.utcnow().shift(minutes=-601).datetime), + 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'price': 0.00001099, 'amount': 90.99181073, 'filled': 0.0, @@ -1094,6 +1097,7 @@ def limit_sell_order_old(): 'type': 'limit', 'side': 'sell', 'symbol': 'ETH/BTC', + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'price': 0.00001099, 'amount': 90.99181073, @@ -1110,6 +1114,7 @@ def limit_buy_order_old_partial(): 'type': 'limit', 'side': 'buy', 'symbol': 'ETH/BTC', + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'price': 0.00001099, 'amount': 90.99181073, @@ -1139,7 +1144,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': '1234512345', 'clientOrderId': None, - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', @@ -1160,7 +1165,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': 'AZNPFF-4AC4N-7MKTAT', 'clientOrderId': None, - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'status': 'canceled', @@ -1181,7 +1186,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': '1234512345', 'clientOrderId': 'alb1234123', - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', @@ -1202,7 +1207,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': '1234512345', 'clientOrderId': 'alb1234123', - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', @@ -1228,7 +1233,7 @@ def limit_sell_order_open(): 'side': 'sell', 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), - 'timestamp': arrow.utcnow().int_timestamp, + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'price': 0.00001173, 'amount': 90.99181073, 'filled': 0.0, @@ -1394,7 +1399,7 @@ def tickers(): 'BLK/BTC': { 'symbol': 'BLK/BTC', 'timestamp': 1522014806072, - 'datetime': '2018-03-25T21:53:26.720Z', + 'datetime': '2018-03-25T21:53:26.072Z', 'high': 0.007745, 'low': 0.007512, 'bid': 0.007729, @@ -1890,7 +1895,8 @@ def buy_order_fee(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', - 'datetime': str(arrow.utcnow().shift(minutes=-601).datetime), + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, + 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'price': 0.245441, 'amount': 8.0, 'cost': 1.963528, @@ -2199,7 +2205,7 @@ def limit_buy_order_usdt_open(): 'side': 'buy', 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), - 'timestamp': arrow.utcnow().int_timestamp, + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'price': 2.00, 'amount': 30.0, 'filled': 0.0, @@ -2226,7 +2232,7 @@ def limit_sell_order_usdt_open(): 'side': 'sell', 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), - 'timestamp': arrow.utcnow().int_timestamp, + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'price': 2.20, 'amount': 30.0, 'filled': 0.0, @@ -2251,6 +2257,7 @@ def market_buy_order_usdt(): 'type': 'market', 'side': 'buy', 'symbol': 'mocked', + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'datetime': arrow.utcnow().isoformat(), 'price': 2.00, 'amount': 30.0, @@ -2307,6 +2314,7 @@ def market_sell_order_usdt(): 'type': 'market', 'side': 'sell', 'symbol': 'mocked', + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'datetime': arrow.utcnow().isoformat(), 'price': 2.20, 'amount': 30.0, diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index ff8383997..b76cb23e6 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1098,7 +1098,7 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.create_order( - pair='ETH/BTC', ordertype=ordertype, side=side, amount=1, rate=200) + pair='ETH/BTC', ordertype=ordertype, side=side, amount=1, rate=rate) assert 'id' in order assert 'info' in order diff --git a/tests/strategy/strats/hyperoptable_strategy.py b/tests/strategy/strats/hyperoptable_strategy.py index 88bdd078e..dc6b03a3e 100644 --- a/tests/strategy/strats/hyperoptable_strategy.py +++ b/tests/strategy/strats/hyperoptable_strategy.py @@ -1,14 +1,13 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement -import talib.abstract as ta from pandas import DataFrame +from strategy_test_v2 import StrategyTestV2 import freqtrade.vendor.qtpylib.indicators as qtpylib -from freqtrade.strategy import (BooleanParameter, DecimalParameter, IntParameter, IStrategy, - RealParameter) +from freqtrade.strategy import BooleanParameter, DecimalParameter, IntParameter, RealParameter -class HyperoptableStrategy(IStrategy): +class HyperoptableStrategy(StrategyTestV2): """ Default Strategy provided by freqtrade bot. Please do not modify this strategy, it's intended for internal use only. @@ -16,38 +15,6 @@ class HyperoptableStrategy(IStrategy): or strategy repository https://github.com/freqtrade/freqtrade-strategies for samples and inspiration. """ - INTERFACE_VERSION = 2 - - # Minimal ROI designed for the strategy - minimal_roi = { - "40": 0.0, - "30": 0.01, - "20": 0.02, - "0": 0.04 - } - - # Optimal stoploss designed for the strategy - stoploss = -0.10 - - # Optimal ticker interval for the strategy - timeframe = '5m' - - # Optional order type mapping - order_types = { - 'buy': 'limit', - 'sell': 'limit', - 'stoploss': 'limit', - 'stoploss_on_exchange': False - } - - # Number of candles the strategy requires before producing valid signals - startup_candle_count: int = 20 - - # Optional time in force for orders - order_time_in_force = { - 'buy': 'gtc', - 'sell': 'gtc', - } buy_params = { 'buy_rsi': 35, @@ -91,55 +58,6 @@ class HyperoptableStrategy(IStrategy): """ return [] - def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Adds several different TA indicators to the given DataFrame - - Performance Note: For the best performance be frugal on the number of indicators - you are using. Let uncomment only the indicator you are using in your strategies - or your hyperopt configuration, otherwise you will waste your memory and CPU usage. - :param dataframe: Dataframe with data from the exchange - :param metadata: Additional information, like the currently traded pair - :return: a Dataframe with all mandatory indicators for the strategies - """ - - # Momentum Indicator - # ------------------------------------ - - # ADX - dataframe['adx'] = ta.ADX(dataframe) - - # MACD - macd = ta.MACD(dataframe) - dataframe['macd'] = macd['macd'] - dataframe['macdsignal'] = macd['macdsignal'] - dataframe['macdhist'] = macd['macdhist'] - - # Minus Directional Indicator / Movement - dataframe['minus_di'] = ta.MINUS_DI(dataframe) - - # Plus Directional Indicator / Movement - dataframe['plus_di'] = ta.PLUS_DI(dataframe) - - # RSI - dataframe['rsi'] = ta.RSI(dataframe) - - # Stoch fast - stoch_fast = ta.STOCHF(dataframe) - dataframe['fastd'] = stoch_fast['fastd'] - dataframe['fastk'] = stoch_fast['fastk'] - - # Bollinger bands - bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) - dataframe['bb_lowerband'] = bollinger['lower'] - dataframe['bb_middleband'] = bollinger['mid'] - dataframe['bb_upperband'] = bollinger['upper'] - - # EMA - Exponential Moving Average - dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) - - return dataframe - def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe diff --git a/tests/strategy/strats/strategy_test_v2.py b/tests/strategy/strats/strategy_test_v2.py index c57becdad..59f1f569e 100644 --- a/tests/strategy/strats/strategy_test_v2.py +++ b/tests/strategy/strats/strategy_test_v2.py @@ -7,7 +7,7 @@ from pandas import DataFrame import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.persistence import Trade -from freqtrade.strategy.interface import IStrategy +from freqtrade.strategy import IStrategy class StrategyTestV2(IStrategy): From e85c7ca8ff18f7f139ddef49a0d21a5a71ecd058 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Wed, 30 Mar 2022 09:50:37 +0100 Subject: [PATCH 26/32] remove blank line --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 03a9da87b..b116b261f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ numpy==1.22.3 pandas==1.4.1 pandas-ta==0.3.14b - ccxt==1.77.45 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.2 From 1559692e4729115af3d63225a98e090e013d74c7 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Fri, 8 Apr 2022 11:44:42 +0100 Subject: [PATCH 27/32] Update hyperopt.py remove duplicates from list of asked points --- freqtrade/optimize/hyperopt.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 35f382469..2883199a9 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -422,16 +422,23 @@ class Hyperopt: 5. Repeat until at least `n_points` points in the `asked_non_tried` list 6. Return a list with length truncated at `n_points` ''' + def unique_list(a_list): + seen = [] + for x in a_list: + key = repr(x) + if key not in seen: + seen.append(eval(key)) + return seen i = 0 asked_non_tried: List[List[Any]] = [] is_random: List[bool] = [] while i < 5 and len(asked_non_tried) < n_points: if i < 3: self.opt.cache_ = {} - asked = self.opt.ask(n_points=n_points * 5) + asked = unique_list(self.opt.ask(n_points=n_points * 5)) is_random = [False for _ in range(len(asked))] else: - asked = self.opt.space.rvs(n_samples=n_points * 5) + asked = unique_list(self.opt.space.rvs(n_samples=n_points * 5)) is_random = [True for _ in range(len(asked))] asked_non_tried += [x for x in asked if x not in self.opt.Xi From fa298d6f1c1b4fd57161b14ae16259a759daa84f Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Tue, 12 Apr 2022 23:57:40 +0100 Subject: [PATCH 28/32] fix unique_list logic --- freqtrade/optimize/hyperopt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 748cc0806..0d71e4ff5 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -426,8 +426,8 @@ class Hyperopt: for x in a_list: key = repr(x) if key not in seen: - seen.append(eval(key)) - return seen + seen.append(key) + return [eval(x) for x in seen] i = 0 asked_non_tried: List[List[Any]] = [] is_random: List[bool] = [] From 35cea6dcfa5a816cc223a966f72a1764a7c0fb93 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Wed, 13 Apr 2022 09:36:46 +0100 Subject: [PATCH 29/32] fix unique_list --- freqtrade/optimize/hyperopt.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 0d71e4ff5..24d2b910d 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -422,12 +422,11 @@ class Hyperopt: 6. Return a list with length truncated at `n_points` ''' def unique_list(a_list): - seen = [] - for x in a_list: - key = repr(x) - if key not in seen: - seen.append(key) - return [eval(x) for x in seen] + new_list = [] + for item in a_list: + if item not in new_list: + new_list.append(item) + return new_list i = 0 asked_non_tried: List[List[Any]] = [] is_random: List[bool] = [] From 4acb77305a78940b64f1ee5550130da503dc0ced Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 13 Apr 2022 19:33:27 +0200 Subject: [PATCH 30/32] Don't break when running hyperopt-x tools on old resuts --- freqtrade/optimize/hyperopt_tools.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 83df7e83c..1610b3b5b 100755 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -310,12 +310,15 @@ class HyperoptTools(): if not has_drawdown: # Ensure compatibility with older versions of hyperopt results trials['results_metrics.max_drawdown_account'] = None + if 'is_random' not in trials.columns: + trials['is_random'] = False # New mode, using backtest result for metrics trials['results_metrics.winsdrawslosses'] = trials.apply( lambda x: f"{x['results_metrics.wins']} {x['results_metrics.draws']:>4} " f"{x['results_metrics.losses']:>4}", axis=1) + trials = trials[['Best', 'current_epoch', 'results_metrics.total_trades', 'results_metrics.winsdrawslosses', 'results_metrics.profit_mean', 'results_metrics.profit_total_abs', From 340c0ea391f4ac89b01352ff6d4fd947715a694b Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Thu, 14 Apr 2022 14:15:11 +0100 Subject: [PATCH 31/32] update is_random before asked_non_tried is_random depends on asked_non_tried and needs to be updated first --- freqtrade/optimize/hyperopt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 24d2b910d..babcc5491 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -438,12 +438,12 @@ class Hyperopt: else: asked = unique_list(self.opt.space.rvs(n_samples=n_points * 5)) is_random = [True for _ in range(len(asked))] - asked_non_tried += [x for x in asked - if x not in self.opt.Xi - and x not in asked_non_tried] is_random += [rand for x, rand in zip(asked, is_random) if x not in self.opt.Xi and x not in asked_non_tried] + asked_non_tried += [x for x in asked + if x not in self.opt.Xi + and x not in asked_non_tried] i += 1 if asked_non_tried: From 1153e65b3ecbb8ec97656a631890c66cf46af165 Mon Sep 17 00:00:00 2001 From: Italo <45588475+italodamato@users.noreply.github.com> Date: Thu, 14 Apr 2022 14:34:04 +0100 Subject: [PATCH 32/32] fix flake8 --- freqtrade/optimize/hyperopt_tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 1610b3b5b..32a095ad8 100755 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -318,7 +318,6 @@ class HyperoptTools(): lambda x: f"{x['results_metrics.wins']} {x['results_metrics.draws']:>4} " f"{x['results_metrics.losses']:>4}", axis=1) - trials = trials[['Best', 'current_epoch', 'results_metrics.total_trades', 'results_metrics.winsdrawslosses', 'results_metrics.profit_mean', 'results_metrics.profit_total_abs',