From 16a516a882f8936df3e4191e0311cedbc9d7a120 Mon Sep 17 00:00:00 2001 From: Italo Date: Wed, 19 Jan 2022 01:50:15 +0000 Subject: [PATCH 001/254] 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 002/254] 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 003/254] 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 004/254] 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 005/254] 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 006/254] 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 007/254] 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 008/254] 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 009/254] 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 010/254] 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 011/254] 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 162e94455b3cf928ac68bd895a7674f1dbe0ce8b Mon Sep 17 00:00:00 2001 From: froggleston Date: Wed, 16 Mar 2022 12:16:24 +0000 Subject: [PATCH 012/254] Add support for storing buy candle indicator rows in backtesting results --- freqtrade/optimize/backtesting.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 79c861ee8..1a8c0903c 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -64,7 +64,8 @@ class Backtesting: config['dry_run'] = True self.strategylist: List[IStrategy] = [] self.all_results: Dict[str, Dict] = {} - + self.processed_dfs: Dict[str, Dict] = {} + self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config) self.dataprovider = DataProvider(self.config, None) @@ -136,6 +137,10 @@ class Backtesting: self.config['startup_candle_count'] = self.required_startup self.exchange.validate_required_startup_candles(self.required_startup, self.timeframe) + self.enable_backtest_signal_candle_export = False + if self.config.get('enable_backtest_signal_candle_export', None) is not None: + self.enable_backtest_signal_candle_export = bool(self.config.get('enable_backtest_signal_candle_export')) + self.progress = BTProgress() self.abort = False @@ -636,6 +641,25 @@ class Backtesting: }) self.all_results[self.strategy.get_strategy_name()] = results + if self.enable_backtest_signal_candle_export: + signal_candles_only = {} + for pair in preprocessed_tmp.keys(): + signal_candles_only_df = DataFrame() + + pairdf = preprocessed_tmp[pair] + resdf = results['results'] + pairresults = resdf.loc[(resdf["pair"] == pair)] + + if pairdf.shape[0] > 0: + for t, v in pairresults.open_date.items(): + allinds = pairdf.loc[(pairdf['date'] < v)] + signal_inds = allinds.iloc[[-1]] + signal_candles_only_df = signal_candles_only_df.append(signal_inds) + + signal_candles_only[pair] = signal_candles_only_df + + self.processed_dfs[self.strategy.get_strategy_name()] = signal_candles_only + return min_date, max_date def start(self) -> None: 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 013/254] 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 014/254] 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 015/254] 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 016/254] 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 017/254] 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 018/254] 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 019/254] 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 020/254] 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 021/254] 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 46acc8352ff384bc2af0ab606443228d1ee7826e Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Tue, 29 Mar 2022 19:19:07 +0200 Subject: [PATCH 022/254] Add selection buttons for trades to forcesell cmd in telegram --- freqtrade/rpc/telegram.py | 65 +++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 16 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 5a20520dd..d5721c04e 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -190,7 +190,8 @@ class Telegram(RPCHandler): pattern='update_sell_reason_performance'), CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'), CallbackQueryHandler(self._count, pattern='update_count'), - CallbackQueryHandler(self._forcebuy_inline), + CallbackQueryHandler(self._forcebuy_inline, pattern="\S+\/\S+"), + CallbackQueryHandler(self._forcesell_inline, pattern="[0-9]+\s\S+\/\S+") ] for handle in handles: self._updater.dispatcher.add_handler(handle) @@ -379,8 +380,6 @@ class Telegram(RPCHandler): first_avg = filled_orders[0]["safe_price"] for x, order in enumerate(filled_orders): - if order['ft_order_side'] != 'buy': - continue cur_entry_datetime = arrow.get(order["order_filled_date"]) cur_entry_amount = order["amount"] cur_entry_average = order["safe_price"] @@ -446,7 +445,7 @@ class Telegram(RPCHandler): messages = [] for r in results: r['open_date_hum'] = arrow.get(r['open_date']).humanize() - r['num_entries'] = len([o for o in r['orders'] if o['ft_order_side'] == 'buy']) + r['num_entries'] = len(r['filled_entry_orders']) r['sell_reason'] = r.get('sell_reason', "") lines = [ "*Trade ID:* `{trade_id}`" + @@ -490,8 +489,8 @@ class Telegram(RPCHandler): lines.append("*Open Order:* `{open_order}`") lines_detail = self._prepare_entry_details( - r['orders'], r['base_currency'], r['is_open']) - lines.extend(lines_detail if lines_detail else "") + r['filled_entry_orders'], r['base_currency'], r['is_open']) + lines.extend((lines_detail if (len(r['filled_entry_orders']) > 1) else "")) # Filter empty lines using list-comprehension messages.append("\n".join([line for line in lines if line]).format(**r)) @@ -909,16 +908,44 @@ class Telegram(RPCHandler): :return: None """ - trade_id = context.args[0] if context.args and len(context.args) > 0 else None - if not trade_id: - self._send_msg("You must specify a trade-id or 'all'.") - return - try: - msg = self._rpc._rpc_forcesell(trade_id) - self._send_msg('Forcesell Result: `{result}`'.format(**msg)) + if context.args: + trade_id = context.args[0] + self._forcesell_action(trade_id) + else: + try: + fiat_currency = self._config.get('fiat_display_currency', '') + statlist, head, fiat_profit_sum = self._rpc._rpc_status_table( + self._config['stake_currency'], fiat_currency) - except RPCException as e: - self._send_msg(str(e)) + trades = [] + for trade in statlist: + trades.append(f"{trade[0]} {trade[1]} {trade[3]}") + + trade_buttons = [ + InlineKeyboardButton(text=trade, callback_data=trade) for trade in trades] + buttons_aligned = self._layout_inline_keyboard_onecol(trade_buttons) + + buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')]) + self._send_msg(msg="Which trade?", + keyboard=buttons_aligned) + + except RPCException as e: + self._send_msg(str(e)) + + def _forcesell_action(self, trade_id): + if trade_id != 'cancel': + try: + self._rpc._rpc_forcesell(trade_id) + except RPCException as e: + self._send_msg(str(e)) + + def _forcesell_inline(self, update: Update, _: CallbackContext) -> None: + if update.callback_query: + query = update.callback_query + trade_id = query.data.split(" ")[0] + query.answer() + query.edit_message_text(text=f"Force Selling: {query.data}") + self._forcesell_action(trade_id) def _forcebuy_action(self, pair, price=None): if pair != 'cancel': @@ -940,6 +967,11 @@ class Telegram(RPCHandler): cols=3) -> List[List[InlineKeyboardButton]]: return [buttons[i:i + cols] for i in range(0, len(buttons), cols)] + @staticmethod + def _layout_inline_keyboard_onecol(buttons: List[InlineKeyboardButton], + cols=1) -> List[List[InlineKeyboardButton]]: + return [buttons[i:i + cols] for i in range(0, len(buttons), cols)] + @authorized_only def _forcebuy(self, update: Update, context: CallbackContext) -> None: """ @@ -949,6 +981,7 @@ class Telegram(RPCHandler): :param update: message update :return: None """ + if context.args: pair = context.args[0] price = float(context.args[1]) if len(context.args) > 1 else None @@ -961,7 +994,7 @@ class Telegram(RPCHandler): buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')]) self._send_msg(msg="Which pair?", - keyboard=buttons_aligned) + keyboard=buttons_aligned) @authorized_only def _trades(self, update: Update, context: CallbackContext) -> None: From 29d6725fb7bbc1d102a4d9e0309159ad4298952a Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Tue, 29 Mar 2022 19:41:49 +0200 Subject: [PATCH 023/254] Allow forcesell to be a valid keyboard option --- freqtrade/rpc/telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index d5721c04e..ac74232aa 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -113,7 +113,7 @@ class Telegram(RPCHandler): r'/stopbuy$', r'/reload_config$', r'/show_config$', r'/logs$', r'/whitelist$', r'/blacklist$', r'/bl_delete$', r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', - r'/forcebuy$', r'/edge$', r'/health$', r'/help$', r'/version$'] + r'/forcebuy$', r'/forcesell$', r'/edge$', r'/health$', r'/help$', r'/version$'] # Create keys for generation valid_keys_print = [k.replace('$', '') for k in valid_keys] @@ -994,7 +994,7 @@ class Telegram(RPCHandler): buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')]) self._send_msg(msg="Which pair?", - keyboard=buttons_aligned) + keyboard=buttons_aligned) @authorized_only def _trades(self, update: Update, context: CallbackContext) -> None: 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 024/254] 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 025/254] 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 026/254] 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 027/254] 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 028/254] 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 029/254] 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 3ed7f3f2df9f459917a008844ff758d561a90c0d Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Wed, 30 Mar 2022 12:28:30 +0200 Subject: [PATCH 030/254] Display all trade info in buttons First step to fix tests for changed forcesell code --- freqtrade/rpc/telegram.py | 4 ++-- tests/rpc/test_rpc_telegram.py | 36 +++++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index ac74232aa..b2448708a 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -919,7 +919,7 @@ class Telegram(RPCHandler): trades = [] for trade in statlist: - trades.append(f"{trade[0]} {trade[1]} {trade[3]}") + trades.append(f"{trade[0]} {trade[1]} {trade[2]} {trade[3]}") trade_buttons = [ InlineKeyboardButton(text=trade, callback_data=trade) for trade in trades] @@ -994,7 +994,7 @@ class Telegram(RPCHandler): buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')]) self._send_msg(msg="Which pair?", - keyboard=buttons_aligned) + keyboard=buttons_aligned) @authorized_only def _trades(self, update: Update, context: CallbackContext) -> None: diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index f53f48cc2..f99328c0f 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1188,15 +1188,6 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: assert msg_mock.call_count == 1 assert 'not running' in msg_mock.call_args_list[0][0][0] - # No argument - msg_mock.reset_mock() - freqtradebot.state = State.RUNNING - context = MagicMock() - context.args = [] - telegram._forcesell(update=update, context=context) - assert msg_mock.call_count == 1 - assert "You must specify a trade-id or 'all'." in msg_mock.call_args_list[0][0][0] - # Invalid argument msg_mock.reset_mock() freqtradebot.state = State.RUNNING @@ -1208,6 +1199,33 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: assert 'invalid argument' in msg_mock.call_args_list[0][0][0] +def test_forcesell_no_tradeid(default_conf, update, mocker) -> None: + + fsell_mock = MagicMock(return_value=None) + mocker.patch('freqtrade.rpc.RPC._rpc_forcesell', fsell_mock) + + telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) + + patch_get_signal(freqtradebot) + + context = MagicMock() + context.args = [] + telegram._forcesell(update=update, context=context) + + assert fsell_mock.call_count == 0 + assert msg_mock.call_count == 1 + assert msg_mock.call_args_list[0][1]['msg'] == 'Which trade?' + # assert msg_mock.call_args_list[0][1]['callback_query_handler'] == 'forcesell' + keyboard = msg_mock.call_args_list[0][1]['keyboard'] + # One additional button - cancel + assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 5 + update = MagicMock() + update.callback_query = MagicMock() + update.callback_query.data = '1 XRP/USDT 1h 2.20% (1.20)' + telegram._forcsell_inline(update, None) + assert fsell_mock.call_count == 1 + + def test_forcebuy_handle(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) From c42af7d09550157e8825e0432b477423ad15cc53 Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Wed, 30 Mar 2022 12:41:41 +0200 Subject: [PATCH 031/254] Fixed typo in test file --- tests/rpc/test_rpc_telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index f99328c0f..39cfbe553 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1222,7 +1222,7 @@ def test_forcesell_no_tradeid(default_conf, update, mocker) -> None: update = MagicMock() update.callback_query = MagicMock() update.callback_query.data = '1 XRP/USDT 1h 2.20% (1.20)' - telegram._forcsell_inline(update, None) + telegram._forcesell_inline(update, None) assert fsell_mock.call_count == 1 From 6c811b3de12d2365fe747b65acaefa81ed52aa1a Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Wed, 30 Mar 2022 19:57:02 +0200 Subject: [PATCH 032/254] Made regex strings raw Removed unwanted changes --- freqtrade/rpc/telegram.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index b2448708a..1aab72ba8 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -190,8 +190,8 @@ class Telegram(RPCHandler): pattern='update_sell_reason_performance'), CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'), CallbackQueryHandler(self._count, pattern='update_count'), - CallbackQueryHandler(self._forcebuy_inline, pattern="\S+\/\S+"), - CallbackQueryHandler(self._forcesell_inline, pattern="[0-9]+\s\S+\/\S+") + CallbackQueryHandler(self._forcebuy_inline, pattern=r"\S+\/\S+"), + CallbackQueryHandler(self._forcesell_inline, pattern=r"[0-9]+\s\S+\/\S+") ] for handle in handles: self._updater.dispatcher.add_handler(handle) @@ -380,6 +380,8 @@ class Telegram(RPCHandler): first_avg = filled_orders[0]["safe_price"] for x, order in enumerate(filled_orders): + if order['ft_order_side'] != 'buy': + continue cur_entry_datetime = arrow.get(order["order_filled_date"]) cur_entry_amount = order["amount"] cur_entry_average = order["safe_price"] @@ -445,7 +447,7 @@ class Telegram(RPCHandler): messages = [] for r in results: r['open_date_hum'] = arrow.get(r['open_date']).humanize() - r['num_entries'] = len(r['filled_entry_orders']) + r['num_entries'] = len([o for o in r['orders'] if o['ft_order_side'] == 'buy']) r['sell_reason'] = r.get('sell_reason', "") lines = [ "*Trade ID:* `{trade_id}`" + @@ -489,8 +491,8 @@ class Telegram(RPCHandler): lines.append("*Open Order:* `{open_order}`") lines_detail = self._prepare_entry_details( - r['filled_entry_orders'], r['base_currency'], r['is_open']) - lines.extend((lines_detail if (len(r['filled_entry_orders']) > 1) else "")) + r['orders'], r['base_currency'], r['is_open']) + lines.extend(lines_detail if lines_detail else "") # Filter empty lines using list-comprehension messages.append("\n".join([line for line in lines if line]).format(**r)) From 3d8cfa7ea551148b2439a4d9e17b0661f6190511 Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Thu, 31 Mar 2022 08:30:20 +0200 Subject: [PATCH 033/254] Several fixes Code optimizations --- freqtrade/rpc/telegram.py | 28 ++++++++++++---------------- tests/rpc/test_rpc_telegram.py | 27 --------------------------- 2 files changed, 12 insertions(+), 43 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 1aab72ba8..84e0e6f50 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -914,25 +914,21 @@ class Telegram(RPCHandler): trade_id = context.args[0] self._forcesell_action(trade_id) else: - try: - fiat_currency = self._config.get('fiat_display_currency', '') - statlist, head, fiat_profit_sum = self._rpc._rpc_status_table( - self._config['stake_currency'], fiat_currency) + fiat_currency = self._config.get('fiat_display_currency', '') + statlist, head, fiat_profit_sum = self._rpc._rpc_status_table( + self._config['stake_currency'], fiat_currency) - trades = [] - for trade in statlist: - trades.append(f"{trade[0]} {trade[1]} {trade[2]} {trade[3]}") + trades = [] + for trade in statlist: + trades.append(f"{trade[0]} {trade[1]} {trade[2]} {trade[3]}") - trade_buttons = [ - InlineKeyboardButton(text=trade, callback_data=trade) for trade in trades] - buttons_aligned = self._layout_inline_keyboard_onecol(trade_buttons) + trade_buttons = [ + InlineKeyboardButton(text=trade, callback_data=trade) for trade in trades] + buttons_aligned = self._layout_inline_keyboard_onecol(trade_buttons) - buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')]) - self._send_msg(msg="Which trade?", - keyboard=buttons_aligned) - - except RPCException as e: - self._send_msg(str(e)) + buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')]) + self._send_msg(msg="Which trade?", + keyboard=buttons_aligned) def _forcesell_action(self, trade_id): if trade_id != 'cancel': diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 39cfbe553..5ce14998e 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1199,33 +1199,6 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: assert 'invalid argument' in msg_mock.call_args_list[0][0][0] -def test_forcesell_no_tradeid(default_conf, update, mocker) -> None: - - fsell_mock = MagicMock(return_value=None) - mocker.patch('freqtrade.rpc.RPC._rpc_forcesell', fsell_mock) - - telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - - patch_get_signal(freqtradebot) - - context = MagicMock() - context.args = [] - telegram._forcesell(update=update, context=context) - - assert fsell_mock.call_count == 0 - assert msg_mock.call_count == 1 - assert msg_mock.call_args_list[0][1]['msg'] == 'Which trade?' - # assert msg_mock.call_args_list[0][1]['callback_query_handler'] == 'forcesell' - keyboard = msg_mock.call_args_list[0][1]['keyboard'] - # One additional button - cancel - assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 5 - update = MagicMock() - update.callback_query = MagicMock() - update.callback_query.data = '1 XRP/USDT 1h 2.20% (1.20)' - telegram._forcesell_inline(update, None) - assert fsell_mock.call_count == 1 - - def test_forcebuy_handle(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) From 6df15a7af9aa59740af1802fd4e2b7da02132279 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 19 Mar 2022 18:54:11 -0600 Subject: [PATCH 034/254] Recursively search subdirectories in user_data/strategies for a strategy --- freqtrade/resolvers/iresolver.py | 13 +++++++++---- freqtrade/resolvers/strategy_resolver.py | 10 ++++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 3ab461041..cddc8b84d 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -44,7 +44,7 @@ class IResolver: @classmethod def build_search_paths(cls, config: Dict[str, Any], user_subdir: Optional[str] = None, - extra_dir: Optional[str] = None) -> List[Path]: + extra_dirs: Optional[List[str]] = None) -> List[Path]: abs_paths: List[Path] = [] if cls.initial_search_path: @@ -53,9 +53,10 @@ class IResolver: if user_subdir: abs_paths.insert(0, config['user_data_dir'].joinpath(user_subdir)) - if extra_dir: + if extra_dirs: # Add extra directory to the top of the search paths - abs_paths.insert(0, Path(extra_dir).resolve()) + for dir in extra_dirs: + abs_paths.insert(0, Path(dir).resolve()) return abs_paths @@ -164,9 +165,13 @@ class IResolver: :return: Object instance or None """ + extra_dirs: List[str] = [] + if extra_dir: + extra_dirs.append(extra_dir) + abs_paths = cls.build_search_paths(config, user_subdir=cls.user_subdir, - extra_dir=extra_dir) + extra_dirs=extra_dirs) found_object = cls._load_object(paths=abs_paths, object_name=object_name, kwargs=kwargs) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 8ad7cdb59..4f5d22e45 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -7,8 +7,9 @@ import logging import tempfile from base64 import urlsafe_b64decode from inspect import getfullargspec +from os import walk from pathlib import Path -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional from freqtrade.constants import REQUIRED_ORDERTIF, REQUIRED_ORDERTYPES, USERPATH_STRATEGIES from freqtrade.exceptions import OperationalException @@ -166,10 +167,15 @@ class StrategyResolver(IResolver): :param extra_dir: additional directory to search for the given strategy :return: Strategy instance or None """ + extra_dirs: List[str] = [ + path[0] for path in walk(f"{config['user_data_dir']}/{USERPATH_STRATEGIES}") + ] # sub-directories + if extra_dir: + extra_dirs.append(extra_dir) abs_paths = StrategyResolver.build_search_paths(config, user_subdir=USERPATH_STRATEGIES, - extra_dir=extra_dir) + extra_dirs=extra_dirs) if ":" in strategy_name: logger.info("loading base64 encoded strategy") From 185daf5772fc1e42d014898eedc8bbbebd4bcfb8 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 20 Mar 2022 03:02:14 -0600 Subject: [PATCH 035/254] add recursive command line option --- freqtrade/commands/cli_options.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index f30c25ba1..955c1ae53 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -82,6 +82,11 @@ AVAILABLE_CLI_OPTIONS = { help='Reset sample files to their original state.', action='store_true', ), + "recursive": Arg( + '-r', '--recursive', + help='Recursively search for a strategy in the strategies folder.', + metavar='store_true', + ), # Main options "strategy": Arg( '-s', '--strategy', From f44ae494fb0c51510d27176d21cc17fd201b7234 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 31 Mar 2022 08:11:05 -0600 Subject: [PATCH 036/254] Added recursive to configuration --- freqtrade/configuration/configuration.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 1ba17a04d..916c2b675 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -267,6 +267,12 @@ class Configuration: self._args_to_config(config, argname='strategy_list', logstring='Using strategy list of {} strategies', logfun=len) + self._args_to_config( + config, + argname='recursive', + logstring='Recursively searching for a strategy in the strategies folder.', + ) + self._args_to_config(config, argname='timeframe', logstring='Overriding timeframe with Command line argument') From b4b809ff8e19e3a0918cc8ff9e6ad1dbbb538077 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 31 Mar 2022 08:16:21 -0600 Subject: [PATCH 037/254] changed recursive to recursive_strategy_search --- freqtrade/commands/cli_options.py | 4 ++-- freqtrade/configuration/configuration.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 955c1ae53..2ed42b299 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -82,8 +82,8 @@ AVAILABLE_CLI_OPTIONS = { help='Reset sample files to their original state.', action='store_true', ), - "recursive": Arg( - '-r', '--recursive', + "recursive_strategy_search": Arg( + '-r', '--recursive_strategy_search', help='Recursively search for a strategy in the strategies folder.', metavar='store_true', ), diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 916c2b675..ae3ed45be 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -269,7 +269,7 @@ class Configuration: self._args_to_config( config, - argname='recursive', + argname='recursive_strategy_search', logstring='Recursively searching for a strategy in the strategies folder.', ) From 2fe5a1594f153b68bc576ebaaa0ceeb8397d5279 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Thu, 31 Mar 2022 08:16:41 -0600 Subject: [PATCH 038/254] Add conditional to recursive strategy searching if in config --- freqtrade/resolvers/strategy_resolver.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 4f5d22e45..44f02e232 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -167,9 +167,13 @@ class StrategyResolver(IResolver): :param extra_dir: additional directory to search for the given strategy :return: Strategy instance or None """ - extra_dirs: List[str] = [ - path[0] for path in walk(f"{config['user_data_dir']}/{USERPATH_STRATEGIES}") - ] # sub-directories + if config['recursive_strategy_search']: + extra_dirs: List[str] = [ + path[0] for path in walk(f"{config['user_data_dir']}/{USERPATH_STRATEGIES}") + ] # sub-directories + else: + extra_dirs = [] + if extra_dir: extra_dirs.append(extra_dir) From f029702bd18232955df71e16632ce0235e678e03 Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Fri, 1 Apr 2022 09:16:35 +0200 Subject: [PATCH 039/254] Fixed flake8 issues --- freqtrade/rpc/telegram.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 84e0e6f50..e1fe8fa20 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -113,7 +113,8 @@ class Telegram(RPCHandler): r'/stopbuy$', r'/reload_config$', r'/show_config$', r'/logs$', r'/whitelist$', r'/blacklist$', r'/bl_delete$', r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', - r'/forcebuy$', r'/forcesell$', r'/edge$', r'/health$', r'/help$', r'/version$'] + r'/forcebuy$', r'/forcesell$', r'/edge$', r'/health$', r'/help$', + r'/version$'] # Create keys for generation valid_keys_print = [k.replace('$', '') for k in valid_keys] @@ -927,9 +928,8 @@ class Telegram(RPCHandler): buttons_aligned = self._layout_inline_keyboard_onecol(trade_buttons) buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')]) - self._send_msg(msg="Which trade?", - keyboard=buttons_aligned) - + self._send_msg(msg="Which trade?", keyboard=buttons_aligned) + def _forcesell_action(self, trade_id): if trade_id != 'cancel': try: @@ -962,12 +962,12 @@ class Telegram(RPCHandler): @staticmethod def _layout_inline_keyboard(buttons: List[InlineKeyboardButton], - cols=3) -> List[List[InlineKeyboardButton]]: + cols=3) -> List[List[InlineKeyboardButton]]: return [buttons[i:i + cols] for i in range(0, len(buttons), cols)] @staticmethod def _layout_inline_keyboard_onecol(buttons: List[InlineKeyboardButton], - cols=1) -> List[List[InlineKeyboardButton]]: + cols=1) -> List[List[InlineKeyboardButton]]: return [buttons[i:i + cols] for i in range(0, len(buttons), cols)] @authorized_only @@ -991,8 +991,7 @@ class Telegram(RPCHandler): buttons_aligned = self._layout_inline_keyboard(pair_buttons) buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')]) - self._send_msg(msg="Which pair?", - keyboard=buttons_aligned) + self._send_msg(msg="Which pair?", keyboard=buttons_aligned) @authorized_only def _trades(self, update: Update, context: CallbackContext) -> None: From 936ada56991333bffbdaa01b0673c6fc12691dc5 Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Sun, 3 Apr 2022 09:58:55 +0200 Subject: [PATCH 040/254] Fixed syntax error --- freqtrade/rpc/telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 82aff13d2..c1899d4a8 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -198,7 +198,7 @@ class Telegram(RPCHandler): CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'), CallbackQueryHandler(self._count, pattern='update_count'), CallbackQueryHandler(self._forcebuy_inline, pattern=r"\S+\/\S+"), - CallbackQueryHandler(self._forcesell_inline, pattern=r"[0-9]+\s\S+\/\S+") + CallbackQueryHandler(self._forcesell_inline, pattern=r"[0-9]+\s\S+\/\S+"), CallbackQueryHandler(self._forceenter_inline), ] for handle in handles: From dd61886341b2be2f532f70249a60e626339d58f7 Mon Sep 17 00:00:00 2001 From: Ron Klinkien Date: Sun, 3 Apr 2022 12:29:29 +0200 Subject: [PATCH 041/254] Readded missing keyboard commands Rename forcesell methods to forceexit --- freqtrade/rpc/telegram.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index c1899d4a8..b335b8a46 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -114,8 +114,8 @@ class Telegram(RPCHandler): r'/stopbuy$', r'/reload_config$', r'/show_config$', r'/logs$', r'/whitelist$', r'/blacklist$', r'/bl_delete$', r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', - r'/forcebuy$', r'/forcesell$', r'/edge$', r'/health$', r'/help$', - r'/version$'] + r'/forcelong$', r'/forceshort$', r'/forcebuy$', r'/forcesell$', + r'/edge$', r'/health$', r'/help$', r'/version$'] # Create keys for generation valid_keys_print = [k.replace('$', '') for k in valid_keys] @@ -197,9 +197,8 @@ class Telegram(RPCHandler): pattern='update_sell_reason_performance'), CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'), CallbackQueryHandler(self._count, pattern='update_count'), - CallbackQueryHandler(self._forcebuy_inline, pattern=r"\S+\/\S+"), - CallbackQueryHandler(self._forcesell_inline, pattern=r"[0-9]+\s\S+\/\S+"), - CallbackQueryHandler(self._forceenter_inline), + CallbackQueryHandler(self._forceenter_inline, pattern=r"\S+\/\S+"), + CallbackQueryHandler(self._forceexit_inline, pattern=r"[0-9]+\s\S+\/\S+") ] for handle in handles: self._updater.dispatcher.add_handler(handle) @@ -943,7 +942,7 @@ class Telegram(RPCHandler): if context.args: trade_id = context.args[0] - self._forcesell_action(trade_id) + self._forceexit_action(trade_id) else: fiat_currency = self._config.get('fiat_display_currency', '') statlist, head, fiat_profit_sum = self._rpc._rpc_status_table( @@ -960,20 +959,20 @@ class Telegram(RPCHandler): buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')]) self._send_msg(msg="Which trade?", keyboard=buttons_aligned) - def _forcesell_action(self, trade_id): + def _forceexit_action(self, trade_id): if trade_id != 'cancel': try: - self._rpc._rpc_forcesell(trade_id) + self._rpc._rpc_forceexit(trade_id) except RPCException as e: self._send_msg(str(e)) - def _forcesell_inline(self, update: Update, _: CallbackContext) -> None: + def _forceexit_inline(self, update: Update, _: CallbackContext) -> None: if update.callback_query: query = update.callback_query trade_id = query.data.split(" ")[0] query.answer() - query.edit_message_text(text=f"Force Selling: {query.data}") - self._forcesell_action(trade_id) + query.edit_message_text(text=f"Manually exiting: {query.data}") + self._forceexit_action(trade_id) def _forceenter_action(self, pair, price: Optional[float], order_side: SignalDirection): if pair != 'cancel': From 82e9f62381d4747dc87994349646b261e9a7a88d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 5 Apr 2022 20:27:32 +0200 Subject: [PATCH 042/254] Add missing setting in arguments.py --- freqtrade/commands/arguments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 28f7d7148..2fb8d3258 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -12,7 +12,7 @@ from freqtrade.constants import DEFAULT_CONFIG ARGS_COMMON = ["verbosity", "logfile", "version", "config", "datadir", "user_data_dir"] -ARGS_STRATEGY = ["strategy", "strategy_path"] +ARGS_STRATEGY = ["strategy", "strategy_path", "recursive_strategy_search"] ARGS_TRADE = ["db_url", "sd_notify", "dry_run", "dry_run_wallet", "fee"] 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 043/254] 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 1ea49ce864be5b40d30b5ff9a5e39a92d824d2b6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 7 Apr 2022 20:29:03 +0200 Subject: [PATCH 044/254] Support nested configurations --- freqtrade/configuration/load_config.py | 30 ++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/freqtrade/configuration/load_config.py b/freqtrade/configuration/load_config.py index 254ce3126..8718e9fd6 100644 --- a/freqtrade/configuration/load_config.py +++ b/freqtrade/configuration/load_config.py @@ -75,18 +75,36 @@ def load_config_file(path: str) -> Dict[str, Any]: return config -def load_from_files(files: List[str]) -> Dict[str, Any]: - +def load_from_files(files: List[str], base_path: Path = None, level: int = 0) -> Dict[str, Any]: + """ + Recursively load configuration files if specified. + Sub-files are assumed to be relative to the initial config. + """ config: Dict[str, Any] = {} + if level > 5: + raise OperationalException("Config loop detected.") if not files: return deepcopy(MINIMAL_CONFIG) # We expect here a list of config filenames - for path in files: - logger.info(f'Using config: {path} ...') - # Merge config options, overwriting old values - config = deep_merge_dicts(load_config_file(path), config) + for filename in files: + logger.info(f'Using config: {filename} ...') + if filename == '-': + # Immediately load stdin and return + return load_config_file(filename) + file = Path(filename) + if base_path: + # Prepend basepath to allow for relative assignments + file = base_path / file + + config_tmp = load_config_file(str(file)) + if 'files' in config_tmp: + config_sub = load_from_files(config_tmp['files'], file.resolve().parent, level + 1) + deep_merge_dicts(config_sub, config_tmp) + + # Merge config options, overwriting prior values + config = deep_merge_dicts(config_tmp, config) config['config_files'] = files From 3427df06539362f6f5838c537ee63d497a5c3778 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Apr 2022 16:04:54 +0200 Subject: [PATCH 045/254] Add simple test for recursive loading --- config_examples/config_full.example.json | 1 + freqtrade/constants.py | 17 +++++++-------- tests/test_configuration.py | 23 ++++++++++++++++++++- tests/testdata/testconfigs/base_config.json | 12 +++++++++++ tests/testdata/testconfigs/pricing.json | 21 +++++++++++++++++++ tests/testdata/testconfigs/pricing2.json | 18 ++++++++++++++++ tests/testdata/testconfigs/recursive.json | 6 ++++++ tests/testdata/testconfigs/testconfig.json | 6 ++++++ 8 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 tests/testdata/testconfigs/base_config.json create mode 100644 tests/testdata/testconfigs/pricing.json create mode 100644 tests/testdata/testconfigs/pricing2.json create mode 100644 tests/testdata/testconfigs/recursive.json create mode 100644 tests/testdata/testconfigs/testconfig.json diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json index 915db6c44..b41acb726 100644 --- a/config_examples/config_full.example.json +++ b/config_examples/config_full.example.json @@ -182,6 +182,7 @@ "disable_dataframe_checks": false, "strategy": "SampleStrategy", "strategy_path": "user_data/strategies/", + "files": [], "dataformat_ohlcv": "json", "dataformat_trades": "jsongz" } diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 8067c1f6a..c6a2ab5d3 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -91,15 +91,14 @@ SUPPORTED_FIAT = [ ] MINIMAL_CONFIG = { - 'stake_currency': '', - 'dry_run': True, - 'exchange': { - 'name': '', - 'key': '', - 'secret': '', - 'pair_whitelist': [], - 'ccxt_async_config': { - 'enableRateLimit': True, + "stake_currency": "", + "dry_run": True, + "exchange": { + "name": "", + "key": "", + "secret": "", + "pair_whitelist": [], + "ccxt_async_config": { } } } diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 19355b9eb..39e56f075 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -18,7 +18,8 @@ from freqtrade.configuration.deprecated_settings import (check_conflicting_setti process_removed_setting, process_temporary_deprecated_settings) from freqtrade.configuration.environment_vars import flat_vars_to_nested_dict -from freqtrade.configuration.load_config import load_config_file, load_file, log_config_error_range +from freqtrade.configuration.load_config import (load_config_file, load_file, load_from_files, + log_config_error_range) from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_VAR_PREFIX from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException @@ -206,6 +207,26 @@ def test_from_config(default_conf, mocker, caplog) -> None: assert isinstance(validated_conf['user_data_dir'], Path) +def test_from_recursive_files(testdatadir) -> None: + files = testdatadir / "testconfigs/testconfig.json" + + conf = Configuration.from_files([files]) + + assert conf + # Exchange comes from "the first config" + assert conf['exchange'] + # Pricing comes from the 2nd config + assert conf['entry_pricing'] + assert conf['entry_pricing']['price_side'] == "same" + assert conf['exit_pricing'] + # The other key comes from pricing2, which is imported by pricing.json + assert conf['exit_pricing']['price_side'] == "other" + + files = testdatadir / "testconfigs/recursive.json" + with pytest.raises(OperationalException, match="Config loop detected."): + load_from_files([files]) + + def test_print_config(default_conf, mocker, caplog) -> None: conf1 = deepcopy(default_conf) # Delete non-json elements from default_conf diff --git a/tests/testdata/testconfigs/base_config.json b/tests/testdata/testconfigs/base_config.json new file mode 100644 index 000000000..d15c5890b --- /dev/null +++ b/tests/testdata/testconfigs/base_config.json @@ -0,0 +1,12 @@ +{ + "stake_currency": "", + "dry_run": true, + "exchange": { + "name": "", + "key": "", + "secret": "", + "pair_whitelist": [], + "ccxt_async_config": { + } + } +} diff --git a/tests/testdata/testconfigs/pricing.json b/tests/testdata/testconfigs/pricing.json new file mode 100644 index 000000000..d8868443f --- /dev/null +++ b/tests/testdata/testconfigs/pricing.json @@ -0,0 +1,21 @@ +{ + "entry_pricing": { + "price_side": "same", + "use_order_book": true, + "order_book_top": 1, + "price_last_balance": 0.0, + "check_depth_of_market": { + "enabled": false, + "bids_to_ask_delta": 1 + } + }, + "exit_pricing":{ + "price_side": "same", + "use_order_book": true, + "order_book_top": 1, + "price_last_balance": 0.0 + }, + "files": [ + "pricing2.json" + ] +} diff --git a/tests/testdata/testconfigs/pricing2.json b/tests/testdata/testconfigs/pricing2.json new file mode 100644 index 000000000..094783a60 --- /dev/null +++ b/tests/testdata/testconfigs/pricing2.json @@ -0,0 +1,18 @@ +{ + "entry_pricing": { + "price_side": "same", + "use_order_book": true, + "order_book_top": 1, + "price_last_balance": 0.0, + "check_depth_of_market": { + "enabled": false, + "bids_to_ask_delta": 1 + } + }, + "exit_pricing":{ + "price_side": "other", + "use_order_book": true, + "order_book_top": 1, + "price_last_balance": 0.0 + } +} diff --git a/tests/testdata/testconfigs/recursive.json b/tests/testdata/testconfigs/recursive.json new file mode 100644 index 000000000..28d8ce05a --- /dev/null +++ b/tests/testdata/testconfigs/recursive.json @@ -0,0 +1,6 @@ +{ + // This file fails as it's loading itself over and over + "files": [ + "./recursive.json" + ] +} diff --git a/tests/testdata/testconfigs/testconfig.json b/tests/testdata/testconfigs/testconfig.json new file mode 100644 index 000000000..557926097 --- /dev/null +++ b/tests/testdata/testconfigs/testconfig.json @@ -0,0 +1,6 @@ +{ + "files": [ + "base_config.json", + "pricing.json" + ] +} From 1435d269962f74180df2fbd22e96de751094ddde Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Apr 2022 17:26:51 +0200 Subject: [PATCH 046/254] store config-file loading paths --- freqtrade/configuration/load_config.py | 7 +++++-- tests/test_configuration.py | 6 ++++++ .../{base_config.json => test_base_config.json} | 0 .../testconfigs/{pricing2.json => test_pricing2_conf.json} | 0 .../testconfigs/{pricing.json => test_pricing_conf.json} | 2 +- tests/testdata/testconfigs/testconfig.json | 4 ++-- 6 files changed, 14 insertions(+), 5 deletions(-) rename tests/testdata/testconfigs/{base_config.json => test_base_config.json} (100%) rename tests/testdata/testconfigs/{pricing2.json => test_pricing2_conf.json} (100%) rename tests/testdata/testconfigs/{pricing.json => test_pricing_conf.json} (92%) diff --git a/freqtrade/configuration/load_config.py b/freqtrade/configuration/load_config.py index 8718e9fd6..5a86ab24a 100644 --- a/freqtrade/configuration/load_config.py +++ b/freqtrade/configuration/load_config.py @@ -86,7 +86,7 @@ def load_from_files(files: List[str], base_path: Path = None, level: int = 0) -> if not files: return deepcopy(MINIMAL_CONFIG) - + files_loaded = [] # We expect here a list of config filenames for filename in files: logger.info(f'Using config: {filename} ...') @@ -101,11 +101,14 @@ def load_from_files(files: List[str], base_path: Path = None, level: int = 0) -> config_tmp = load_config_file(str(file)) if 'files' in config_tmp: config_sub = load_from_files(config_tmp['files'], file.resolve().parent, level + 1) + files_loaded.extend(config_sub.get('config_files', [])) deep_merge_dicts(config_sub, config_tmp) + files_loaded.insert(0, str(file)) + # Merge config options, overwriting prior values config = deep_merge_dicts(config_tmp, config) - config['config_files'] = files + config['config_files'] = files_loaded return config diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 39e56f075..957468b86 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -222,6 +222,12 @@ def test_from_recursive_files(testdatadir) -> None: # The other key comes from pricing2, which is imported by pricing.json assert conf['exit_pricing']['price_side'] == "other" + assert len(conf['config_files']) == 4 + assert 'testconfig.json' in conf['config_files'][0] + assert 'test_pricing_conf.json' in conf['config_files'][1] + assert 'test_base_config.json' in conf['config_files'][2] + assert 'test_pricing2_conf.json' in conf['config_files'][3] + files = testdatadir / "testconfigs/recursive.json" with pytest.raises(OperationalException, match="Config loop detected."): load_from_files([files]) diff --git a/tests/testdata/testconfigs/base_config.json b/tests/testdata/testconfigs/test_base_config.json similarity index 100% rename from tests/testdata/testconfigs/base_config.json rename to tests/testdata/testconfigs/test_base_config.json diff --git a/tests/testdata/testconfigs/pricing2.json b/tests/testdata/testconfigs/test_pricing2_conf.json similarity index 100% rename from tests/testdata/testconfigs/pricing2.json rename to tests/testdata/testconfigs/test_pricing2_conf.json diff --git a/tests/testdata/testconfigs/pricing.json b/tests/testdata/testconfigs/test_pricing_conf.json similarity index 92% rename from tests/testdata/testconfigs/pricing.json rename to tests/testdata/testconfigs/test_pricing_conf.json index d8868443f..fbdaede74 100644 --- a/tests/testdata/testconfigs/pricing.json +++ b/tests/testdata/testconfigs/test_pricing_conf.json @@ -16,6 +16,6 @@ "price_last_balance": 0.0 }, "files": [ - "pricing2.json" + "./test_pricing2_conf.json" ] } diff --git a/tests/testdata/testconfigs/testconfig.json b/tests/testdata/testconfigs/testconfig.json index 557926097..96b3b6db8 100644 --- a/tests/testdata/testconfigs/testconfig.json +++ b/tests/testdata/testconfigs/testconfig.json @@ -1,6 +1,6 @@ { "files": [ - "base_config.json", - "pricing.json" + "test_base_config.json", + "test_pricing_conf.json" ] } From 238ff6c9fe7fa5681730f42201810ed57bae9c2d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Apr 2022 17:30:23 +0200 Subject: [PATCH 047/254] Use better naming --- config_examples/config_full.example.json | 2 +- freqtrade/configuration/load_config.py | 4 ++-- tests/testdata/testconfigs/recursive.json | 2 +- tests/testdata/testconfigs/test_pricing_conf.json | 2 +- tests/testdata/testconfigs/testconfig.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json index b41acb726..193cc30bc 100644 --- a/config_examples/config_full.example.json +++ b/config_examples/config_full.example.json @@ -182,7 +182,7 @@ "disable_dataframe_checks": false, "strategy": "SampleStrategy", "strategy_path": "user_data/strategies/", - "files": [], + "add_config_files": [], "dataformat_ohlcv": "json", "dataformat_trades": "jsongz" } diff --git a/freqtrade/configuration/load_config.py b/freqtrade/configuration/load_config.py index 5a86ab24a..c6a81d384 100644 --- a/freqtrade/configuration/load_config.py +++ b/freqtrade/configuration/load_config.py @@ -99,8 +99,8 @@ def load_from_files(files: List[str], base_path: Path = None, level: int = 0) -> file = base_path / file config_tmp = load_config_file(str(file)) - if 'files' in config_tmp: - config_sub = load_from_files(config_tmp['files'], file.resolve().parent, level + 1) + if 'add_config_files' in config_tmp: + config_sub = load_from_files(config_tmp['add_config_files'], file.resolve().parent, level + 1) files_loaded.extend(config_sub.get('config_files', [])) deep_merge_dicts(config_sub, config_tmp) diff --git a/tests/testdata/testconfigs/recursive.json b/tests/testdata/testconfigs/recursive.json index 28d8ce05a..33ab12008 100644 --- a/tests/testdata/testconfigs/recursive.json +++ b/tests/testdata/testconfigs/recursive.json @@ -1,6 +1,6 @@ { // This file fails as it's loading itself over and over - "files": [ + "add_config_files": [ "./recursive.json" ] } diff --git a/tests/testdata/testconfigs/test_pricing_conf.json b/tests/testdata/testconfigs/test_pricing_conf.json index fbdaede74..59516d65e 100644 --- a/tests/testdata/testconfigs/test_pricing_conf.json +++ b/tests/testdata/testconfigs/test_pricing_conf.json @@ -15,7 +15,7 @@ "order_book_top": 1, "price_last_balance": 0.0 }, - "files": [ + "add_config_files": [ "./test_pricing2_conf.json" ] } diff --git a/tests/testdata/testconfigs/testconfig.json b/tests/testdata/testconfigs/testconfig.json index 96b3b6db8..87ed6daef 100644 --- a/tests/testdata/testconfigs/testconfig.json +++ b/tests/testdata/testconfigs/testconfig.json @@ -1,5 +1,5 @@ { - "files": [ + "add_config_files": [ "test_base_config.json", "test_pricing_conf.json" ] From 16e64ddf97e82ef1613c61b3607074e2380dcbd6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Apr 2022 17:36:50 +0200 Subject: [PATCH 048/254] Update docs for multi-config loading --- docs/configuration.md | 24 ++++++++++++++++++++++-- freqtrade/configuration/load_config.py | 3 ++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 49a59c070..0c89bbbdd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -53,14 +53,33 @@ FREQTRADE__EXCHANGE__SECRET= Multiple configuration files can be specified and used by the bot or the bot can read its configuration parameters from the process standard input stream. +You can specify additional configuration files in `add_config_files`. Files specified in this parameter will be loaded and merged with the initial config file. The files are resolved relative to the initial configuration file. +This is similar to using multiple `--config` parameters, but simpler in usage as you don't have to specify all files for all commands. + !!! Tip "Use multiple configuration files to keep secrets secret" You can use a 2nd configuration file containing your secrets. That way you can share your "primary" configuration file, while still keeping your API keys for yourself. + ``` json title="user_data/config.json" + "add_config_files": [ + "config-private.json" + ] + ``` + + ``` bash + freqtrade trade --config user_data/config.json <...> + ``` + + The 2nd file should only specify what you intend to override. + If a key is in more than one of the configurations, then the "last specified configuration" wins (in the above example, `config-private.json`). + + For one-off commands, you can also use the below syntax by specifying multiple "--config" parameters. + ``` bash freqtrade trade --config user_data/config.json --config user_data/config-private.json <...> ``` - The 2nd file should only specify what you intend to override. - If a key is in more than one of the configurations, then the "last specified configuration" wins (in the above example, `config-private.json`). + + This is equivalent to the example above - but `config-private.json` is specified as cli argument. + ## Configuration parameters @@ -175,6 +194,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details.
**Datatype:** Boolean | `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file.
**Datatype:** String | `user_data_dir` | Directory containing user data.
*Defaults to `./user_data/`*.
**Datatype:** String +| `add_config_files` | Additional config files. These files will be loaded and merged with the current config file. The files are resolved relative to the initial file.
*Defaults to `[]`*.
**Datatype:** List of strings | `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data.
*Defaults to `json`*.
**Datatype:** String | `dataformat_trades` | Data format to use to store historical trades data.
*Defaults to `jsongz`*.
**Datatype:** String | `position_adjustment_enable` | Enables the strategy to use position adjustments (additional buys or sells). [More information here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean diff --git a/freqtrade/configuration/load_config.py b/freqtrade/configuration/load_config.py index c6a81d384..32c2ae0d9 100644 --- a/freqtrade/configuration/load_config.py +++ b/freqtrade/configuration/load_config.py @@ -100,7 +100,8 @@ def load_from_files(files: List[str], base_path: Path = None, level: int = 0) -> config_tmp = load_config_file(str(file)) if 'add_config_files' in config_tmp: - config_sub = load_from_files(config_tmp['add_config_files'], file.resolve().parent, level + 1) + config_sub = load_from_files( + config_tmp['add_config_files'], file.resolve().parent, level + 1) files_loaded.extend(config_sub.get('config_files', [])) deep_merge_dicts(config_sub, config_tmp) From 392967a26f8794e95de49d7ea93ca4131a3b2489 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 8 Apr 2022 18:06:51 +0200 Subject: [PATCH 049/254] Update formatting --- freqtrade/rpc/telegram.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 8f11ccbf2..9f86ea580 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -115,9 +115,8 @@ class Telegram(RPCHandler): r'/stopbuy$', r'/reload_config$', r'/show_config$', r'/logs$', r'/whitelist$', r'/blacklist$', r'/bl_delete$', r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', - r'/forcelong$', r'/forceshort$', r'/forcebuy$', r'/forcesell$', + r'/forcebuy$', r'/forcelong$', r'/forceshort$', r'/forcesell$', r'/edge$', r'/health$', r'/help$', r'/version$'] - # Create keys for generation valid_keys_print = [k.replace('$', '') for k in valid_keys] @@ -989,13 +988,13 @@ class Telegram(RPCHandler): self._force_enter_action(pair, None, order_side) @staticmethod - def _layout_inline_keyboard(buttons: List[InlineKeyboardButton], - cols=3) -> List[List[InlineKeyboardButton]]: + def _layout_inline_keyboard( + buttons: List[InlineKeyboardButton], cols=3) -> List[List[InlineKeyboardButton]]: return [buttons[i:i + cols] for i in range(0, len(buttons), cols)] @staticmethod - def _layout_inline_keyboard_onecol(buttons: List[InlineKeyboardButton], - cols=1) -> List[List[InlineKeyboardButton]]: + def _layout_inline_keyboard_onecol( + buttons: List[InlineKeyboardButton], cols=1) -> List[List[InlineKeyboardButton]]: return [buttons[i:i + cols] for i in range(0, len(buttons), cols)] @authorized_only From 40eb3f274f25e9edf3e9342e2cf62790bffdefad Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Apr 2022 08:36:22 +0200 Subject: [PATCH 050/254] Fix merge mistake --- freqtrade/rpc/telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 9f86ea580..fabe718a6 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -958,7 +958,7 @@ class Telegram(RPCHandler): def _forceexit_action(self, trade_id): if trade_id != 'cancel': try: - self._rpc._rpc_forceexit(trade_id) + self._rpc._rpc_force_exit(trade_id) except RPCException as e: self._send_msg(str(e)) From 9cd92ed48ce2217cc4c03a4a59f7afab5eba2619 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Apr 2022 09:24:20 +0200 Subject: [PATCH 051/254] Fix forceexit to work --- freqtrade/rpc/telegram.py | 40 +++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index fabe718a6..c2d050f2b 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -197,8 +197,8 @@ class Telegram(RPCHandler): pattern='update_exit_reason_performance'), CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'), CallbackQueryHandler(self._count, pattern='update_count'), + CallbackQueryHandler(self._force_exit_inline, pattern=r"force_exit__\S+"), CallbackQueryHandler(self._force_enter_inline, pattern=r"\S+\/\S+"), - CallbackQueryHandler(self._forceexit_inline, pattern=r"[0-9]+\s\S+\/\S+") ] for handle in handles: self._updater.dispatcher.add_handler(handle) @@ -938,37 +938,49 @@ class Telegram(RPCHandler): if context.args: trade_id = context.args[0] - self._forceexit_action(trade_id) + self._force_exit_action(trade_id) else: fiat_currency = self._config.get('fiat_display_currency', '') - statlist, head, fiat_profit_sum = self._rpc._rpc_status_table( - self._config['stake_currency'], fiat_currency) - + try: + statlist, head, fiat_profit_sum = self._rpc._rpc_status_table( + self._config['stake_currency'], fiat_currency) + except RPCException: + self._send_msg(msg='No open trade found.') + return trades = [] for trade in statlist: - trades.append(f"{trade[0]} {trade[1]} {trade[2]} {trade[3]}") + trades.append((trade[0], f"{trade[0]} {trade[1]} {trade[2]} {trade[3]}")) trade_buttons = [ - InlineKeyboardButton(text=trade, callback_data=trade) for trade in trades] + InlineKeyboardButton(text=trade[1], callback_data=f"force_exit__{trade[0]}") + for trade in trades] buttons_aligned = self._layout_inline_keyboard_onecol(trade_buttons) - buttons_aligned.append([InlineKeyboardButton(text='Cancel', callback_data='cancel')]) + buttons_aligned.append([InlineKeyboardButton( + text='Cancel', callback_data='force_exit__cancel')]) self._send_msg(msg="Which trade?", keyboard=buttons_aligned) - def _forceexit_action(self, trade_id): + def _force_exit_action(self, trade_id): if trade_id != 'cancel': try: self._rpc._rpc_force_exit(trade_id) except RPCException as e: self._send_msg(str(e)) - def _forceexit_inline(self, update: Update, _: CallbackContext) -> None: + def _force_exit_inline(self, update: Update, _: CallbackContext) -> None: if update.callback_query: query = update.callback_query - trade_id = query.data.split(" ")[0] - query.answer() - query.edit_message_text(text=f"Manually exiting: {query.data}") - self._forceexit_action(trade_id) + if query.data and '__' in query.data: + # Input data is "force_exit__" + trade_id = query.data.split("__")[1].split(' ')[0] + if trade_id == 'cancel': + query.answer() + query.edit_message_text(text="Forcesell canceled") + return + trade: Trade = Trade.get_trades(trade_filter=Trade.id == trade_id).first() + query.answer() + query.edit_message_text(text=f"Manually exiting Trade #{trade_id}, {trade.pair}") + self._force_exit_action(trade_id) def _force_enter_action(self, pair, price: Optional[float], order_side: SignalDirection): if pair != 'cancel': From ddfc68d533a73bd8531b7a98be399f90c4eb1c2b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Apr 2022 09:41:01 +0200 Subject: [PATCH 052/254] Add test case for interactive telegram exit --- freqtrade/rpc/telegram.py | 2 +- tests/rpc/test_rpc_telegram.py | 52 ++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index c2d050f2b..fb86d0481 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -975,7 +975,7 @@ class Telegram(RPCHandler): trade_id = query.data.split("__")[1].split(' ')[0] if trade_id == 'cancel': query.answer() - query.edit_message_text(text="Forcesell canceled") + query.edit_message_text(text="Force exit canceled.") return trade: Trade = Trade.get_trades(trade_filter=Trade.id == trade_id).first() query.answer() diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 641943620..bb863a004 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1067,8 +1067,8 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee, } == last_msg -def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee, - ticker_sell_down, mocker) -> None: +def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee, + ticker_sell_down, mocker) -> None: mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) msg_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) @@ -1222,6 +1222,54 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: assert 'invalid argument' in msg_mock.call_args_list[0][0][0] +def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None: + default_conf['max_open_trades'] = 4 + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker, + get_fee=fee, + _is_dry_limit_order_filled=MagicMock(return_value=True), + ) + femock = mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_exit') + telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) + + patch_get_signal(freqtradebot) + + # Create some test data + freqtradebot.enter_positions() + msg_mock.reset_mock() + + # /forcesell all + context = MagicMock() + context.args = [] + telegram._force_exit(update=update, context=context) + keyboard = msg_mock.call_args_list[0][1]['keyboard'] + # 4 pairs + cancel + assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 5 + assert keyboard[-1][0].text == "Cancel" + + assert keyboard[1][0].callback_data == 'force_exit__2 L' + update = MagicMock() + update.callback_query = MagicMock() + update.callback_query.data = keyboard[1][0].callback_data + telegram._force_exit_inline(update, None) + assert update.callback_query.answer.call_count == 1 + assert update.callback_query.edit_message_text.call_count == 1 + assert femock.call_count == 1 + assert femock.call_args_list[0][0][0] == '2' + + # Retry selling - but cancel instead + update.callback_query.reset_mock() + telegram._force_exit(update=update, context=context) + # Use cancel button + update.callback_query.data = keyboard[-1][0].callback_data + telegram._force_exit_inline(update, None) + query = update.callback_query + assert query.answer.call_count == 1 + assert query.edit_message_text.call_count == 1 + assert query.edit_message_text.call_args_list[-1][1]['text'] == "Force exit canceled." + + def test_force_enter_handle(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) From f385e2c2b6adaa405d5ae111a324dc0b7ff56bf5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Apr 2022 10:04:10 +0200 Subject: [PATCH 053/254] Update test to also cover "no trade found" scenario --- tests/rpc/test_rpc_telegram.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index bb863a004..de777d609 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1235,13 +1235,18 @@ def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None: patch_get_signal(freqtradebot) + # /forceexit + context = MagicMock() + context.args = [] + telegram._force_exit(update=update, context=context) + # No pair + assert msg_mock.call_args_list[0][1]['msg'] == 'No open trade found.' + # Create some test data freqtradebot.enter_positions() msg_mock.reset_mock() - # /forcesell all - context = MagicMock() - context.args = [] + # /forceexit telegram._force_exit(update=update, context=context) keyboard = msg_mock.call_args_list[0][1]['keyboard'] # 4 pairs + cancel @@ -1258,7 +1263,7 @@ def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None: assert femock.call_count == 1 assert femock.call_args_list[0][0][0] == '2' - # Retry selling - but cancel instead + # Retry exiting - but cancel instead update.callback_query.reset_mock() telegram._force_exit(update=update, context=context) # Use cancel button From e6060511028c97036e49a4bb6829efceec262332 Mon Sep 17 00:00:00 2001 From: RafaelDorigo Date: Sat, 9 Apr 2022 11:53:47 +0200 Subject: [PATCH 054/254] Fixed setup.sh --- setup.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 2c3a6710b..5cde1a589 100755 --- a/setup.sh +++ b/setup.sh @@ -89,12 +89,13 @@ function updateenv() { fi echo "pip install completed" echo - if [[ $dev =~ ^[Yy]$ ]] then + if [[ $dev =~ ^[Yy]$ ]]; then ${PYTHON} -m pre-commit install if [ $? -ne 0 ]; then echo "Failed installing pre-commit" exit 1 fi + fi } # Install tab lib From 8e98a2ff9f4fabf81bf5a4f4e1f772f5c4a091ec Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Apr 2022 16:42:18 +0200 Subject: [PATCH 055/254] api - provide assset_currency via API --- freqtrade/exchange/exchange.py | 8 ++------ freqtrade/freqtradebot.py | 3 +++ freqtrade/optimize/backtesting.py | 3 +++ freqtrade/persistence/migrations.py | 14 ++++++++----- freqtrade/persistence/models.py | 26 +++++++++++++++++++++++++ freqtrade/rpc/api_server/api_schemas.py | 2 ++ freqtrade/rpc/rpc.py | 1 - freqtrade/rpc/telegram.py | 2 +- tests/rpc/test_rpc.py | 6 ++++-- tests/rpc/test_rpc_apiserver.py | 8 ++++++-- tests/rpc/test_rpc_telegram.py | 3 ++- tests/test_persistence.py | 4 ++++ 12 files changed, 62 insertions(+), 18 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 609dbb83e..82505759a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -341,15 +341,11 @@ class Exchange: return sorted(set([x['quote'] for _, x in markets.items()])) def get_pair_quote_currency(self, pair: str) -> str: - """ - Return a pair's quote currency - """ + """ Return a pair's quote currency (base/quote:settlement) """ return self.markets.get(pair, {}).get('quote', '') def get_pair_base_currency(self, pair: str) -> str: - """ - Return a pair's base currency - """ + """ Return a pair's base currency (base/quote:settlement) """ return self.markets.get(pair, {}).get('base', '') def market_is_future(self, market: Dict[str, Any]) -> bool: diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index dc2e21ed6..57d7cac3c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -676,6 +676,7 @@ class FreqtradeBot(LoggingMixin): # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') + base_currency = self.exchange.get_pair_base_currency(pair) open_date = datetime.now(timezone.utc) funding_fees = self.exchange.get_funding_fees( pair=pair, amount=amount, is_short=is_short, open_date=open_date) @@ -683,6 +684,8 @@ class FreqtradeBot(LoggingMixin): if trade is None: trade = Trade( pair=pair, + base_currency=base_currency, + stake_currency=self.config['stake_currency'], stake_amount=stake_amount, amount=amount, is_open=True, diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 4bb10d39c..438337669 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -726,6 +726,7 @@ class Backtesting: if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount): self.order_id_counter += 1 + base_currency = self.exchange.get_pair_base_currency(pair) amount = round((stake_amount / propose_rate) * leverage, 8) is_short = (direction == 'short') # Necessary for Margin trading. Disabled until support is enabled. @@ -738,6 +739,8 @@ class Backtesting: id=self.trade_id_counter, open_order_id=self.order_id_counter, pair=pair, + base_currency=base_currency, + stake_currency=self.config['stake_currency'], open_rate=propose_rate, open_rate_requested=propose_rate, open_date=current_time, diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 9521eae69..996af7341 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -58,6 +58,8 @@ def migrate_trades_and_orders_table( decl_base, inspector, engine, trade_back_name: str, cols: List, order_back_name: str, cols_order: List): + base_currency = get_column_def(cols, 'base_currency', 'null') + stake_currency = get_column_def(cols, 'stake_currency', 'null') fee_open = get_column_def(cols, 'fee_open', 'fee') fee_open_cost = get_column_def(cols, 'fee_open_cost', 'null') fee_open_currency = get_column_def(cols, 'fee_open_currency', 'null') @@ -130,7 +132,7 @@ def migrate_trades_and_orders_table( # Copy data back - following the correct schema with engine.begin() as connection: connection.execute(text(f"""insert into trades - (id, exchange, pair, is_open, + (id, exchange, pair, base_currency, stake_currency, is_open, fee_open, fee_open_cost, fee_open_currency, fee_close, fee_close_cost, fee_close_currency, open_rate, open_rate_requested, close_rate, close_rate_requested, close_profit, @@ -142,7 +144,8 @@ def migrate_trades_and_orders_table( trading_mode, leverage, liquidation_price, is_short, interest_rate, funding_fees ) - select id, lower(exchange), pair, + select id, lower(exchange), pair, {base_currency} base_currency, + {stake_currency} stake_currency, is_open, {fee_open} fee_open, {fee_open_cost} fee_open_cost, {fee_open_currency} fee_open_currency, {fee_close} fee_close, {fee_close_cost} fee_close_cost, {fee_close_currency} fee_close_currency, @@ -230,7 +233,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None: """ inspector = inspect(engine) - cols = inspector.get_columns('trades') + cols_trades = inspector.get_columns('trades') cols_orders = inspector.get_columns('orders') tabs = get_table_names_for_table(inspector, 'trades') table_back_name = get_backup_name(tabs, 'trades_bak') @@ -241,11 +244,12 @@ def check_migrate(engine, decl_base, previous_tables) -> None: # Migrates both trades and orders table! # if ('orders' not in previous_tables # or not has_column(cols_orders, 'leverage')): - if not has_column(cols, 'exit_order_status'): + if not has_column(cols_trades, 'base_currency'): logger.info(f"Running database migration for trades - " f"backup: {table_back_name}, {order_table_bak_name}") migrate_trades_and_orders_table( - decl_base, inspector, engine, table_back_name, cols, order_table_bak_name, cols_orders) + decl_base, inspector, engine, table_back_name, cols_trades, + order_table_bak_name, cols_orders) if 'orders' not in previous_tables and 'trades' in previous_tables: logger.info('Moving open orders to Orders table.') diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 3cd9cbd67..05de39caf 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -279,6 +279,8 @@ class LocalTrade(): exchange: str = '' pair: str = '' + base_currency: str = '' + stake_currency: str = '' is_open: bool = True fee_open: float = 0.0 fee_open_cost: Optional[float] = None @@ -397,6 +399,26 @@ class LocalTrade(): else: return "long" + @property + def safe_base_currency(self) -> str: + """ + Compatibility layer for asset - which can be empty for old trades. + """ + try: + return self.base_currency or self.pair.split('/')[0] + except IndexError: + return '' + + @property + def safe_quote_currency(self) -> str: + """ + Compatibility layer for asset - which can be empty for old trades. + """ + try: + return self.stake_currency or self.pair.split('/')[1].split(':')[0] + except IndexError: + return '' + def __init__(self, **kwargs): for key in kwargs: setattr(self, key, kwargs[key]) @@ -423,6 +445,8 @@ class LocalTrade(): return { 'trade_id': self.id, 'pair': self.pair, + 'base_currency': self.safe_base_currency, + 'quote_currency': self.safe_quote_currency, 'is_open': self.is_open, 'exchange': self.exchange, 'amount': round(self.amount, 8), @@ -1051,6 +1075,8 @@ class Trade(_DECL_BASE, LocalTrade): exchange = Column(String(25), nullable=False) pair = Column(String(25), nullable=False, index=True) + base_currency = Column(String(25), nullable=True) + stake_currency = Column(String(25), nullable=True) is_open = Column(Boolean, nullable=False, default=True, index=True) fee_open = Column(Float, nullable=False, default=0.0) fee_open_cost = Column(Float, nullable=True) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 03049e0f4..ae797edad 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -203,6 +203,8 @@ class OrderSchema(BaseModel): class TradeSchema(BaseModel): trade_id: int pair: str + base_currency: str + quote_currency: str is_open: bool is_short: bool exchange: str diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 8f3d57cf6..be0e8e797 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -197,7 +197,6 @@ class RPC: trade_dict = trade.to_json() trade_dict.update(dict( - base_currency=self._freqtrade.config['stake_currency'], close_profit=trade.close_profit if trade.close_profit is not None else None, current_rate=current_rate, current_profit=current_profit, # Deprecated diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index e13e46395..5f6a8b147 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -508,7 +508,7 @@ class Telegram(RPCHandler): lines.append("*Open Order:* `{open_order}`") lines_detail = self._prepare_entry_details( - r['orders'], r['base_currency'], r['is_open']) + r['orders'], r['quote_currency'], r['is_open']) lines.extend(lines_detail if lines_detail else "") # Filter empty lines using list-comprehension diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index e421b6fe5..f4a2f6099 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -52,7 +52,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: assert results[0] == { 'trade_id': 1, 'pair': 'ETH/BTC', - 'base_currency': 'BTC', + 'base_currency': 'ETH', + 'quote_currency': 'BTC', 'open_date': ANY, 'open_timestamp': ANY, 'is_open': ANY, @@ -135,7 +136,8 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: assert results[0] == { 'trade_id': 1, 'pair': 'ETH/BTC', - 'base_currency': 'BTC', + 'base_currency': 'ETH', + 'quote_currency': 'BTC', 'open_date': ANY, 'open_timestamp': ANY, 'is_open': ANY, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 3e1710c8e..76cef0df0 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -931,6 +931,8 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, 'open_order': None, 'open_rate': 0.123, 'pair': 'ETH/BTC', + 'base_currency': 'ETH', + 'quote_currency': 'BTC', 'stake_amount': 0.001, 'stop_loss_abs': ANY, 'stop_loss_pct': ANY, @@ -1097,7 +1099,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): # Test creating trade fbuy_mock = MagicMock(return_value=Trade( - pair='ETH/ETH', + pair='ETH/BTC', amount=1, amount_requested=1, exchange='binance', @@ -1130,7 +1132,9 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): 'open_date': ANY, 'open_timestamp': ANY, 'open_rate': 0.245441, - 'pair': 'ETH/ETH', + 'pair': 'ETH/BTC', + 'base_currency': 'ETH', + 'quote_currency': 'BTC', 'stake_amount': 1, 'stop_loss_abs': None, 'stop_loss_pct': None, diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index f104e7153..da853799b 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -184,7 +184,8 @@ def test_telegram_status(default_conf, update, mocker) -> None: _rpc_trade_status=MagicMock(return_value=[{ 'trade_id': 1, 'pair': 'ETH/BTC', - 'base_currency': 'BTC', + 'base_currency': 'ETH', + 'quote_currency': 'BTC', 'open_date': arrow.utcnow(), 'close_date': None, 'open_rate': 1.099e-05, diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 8ba8764e0..d30d33d3b 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -1561,6 +1561,8 @@ def test_to_json(fee): assert result == {'trade_id': None, 'pair': 'ADA/USDT', + 'base_currency': 'ADA', + 'quote_currency': 'USDT', 'is_open': None, 'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"), 'open_timestamp': int(trade.open_date.timestamp() * 1000), @@ -1637,6 +1639,8 @@ def test_to_json(fee): assert result == {'trade_id': None, 'pair': 'XRP/BTC', + 'base_currency': 'XRP', + 'quote_currency': 'BTC', 'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"), 'open_timestamp': int(trade.open_date.timestamp() * 1000), 'close_date': trade.close_date.strftime("%Y-%m-%d %H:%M:%S"), From ef18d0916123bdfe584ccda8c76792645865b692 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Apr 2022 16:50:38 +0200 Subject: [PATCH 056/254] Call custom_exit also when the trade is not in profit and exit_profit_only is set. --- docs/strategy-callbacks.md | 3 ++- docs/strategy_migration.md | 3 +++ freqtrade/strategy/interface.py | 11 ++++++----- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 678899cc6..302ffd5fd 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -91,7 +91,8 @@ For example you could implement a 1:2 risk-reward ROI with `custom_exit()`. Using custom_exit() signals in place of stoploss though *is not recommended*. It is a inferior method to using `custom_stoploss()` in this regard - which also allows you to keep the stoploss on exchange. !!! Note - Returning a (none-empty) `string` or `True` from this method is equal to setting exit signal on a candle at specified time. This method is not called when exit signal is set already, or if exit signals are disabled (`use_exit_signal=False` or `exit_profit_only=True` while profit is below `exit_profit_offset`). `string` max length is 64 characters. Exceeding this limit will cause the message to be truncated to 64 characters. + Returning a (none-empty) `string` or `True` from this method is equal to setting exit signal on a candle at specified time. This method is not called when exit signal is set already, or if exit signals are disabled (`use_exit_signal=False`). `string` max length is 64 characters. Exceeding this limit will cause the message to be truncated to 64 characters. + `custom_exit()` will ignore `exit_profit_only`, and will always be called unless `use_exit_signal=False` or if there is an enter signal. An example of how we can use different indicators depending on the current profit and also exit trades that were open longer than one day: diff --git a/docs/strategy_migration.md b/docs/strategy_migration.md index eb1729ba7..1fe1f0953 100644 --- a/docs/strategy_migration.md +++ b/docs/strategy_migration.md @@ -145,6 +145,9 @@ Please refer to the [Strategy documentation](strategy-customization.md#exit-sign ### `custom_sell` +`custom_sell` has been renamed to `custom_exit`. +It's now also being called for every iteration, independent of current profit and `exit_profit_only` settings. + ``` python hl_lines="2" class AwesomeStrategy(IStrategy): def custom_sell(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float, diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index b0ed6e72d..1c53b2e3e 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -881,10 +881,7 @@ class IStrategy(ABC, HyperStrategyMixin): current_rate = rate current_profit = trade.calc_profit_ratio(current_rate) - if (self.exit_profit_only and current_profit <= self.exit_profit_offset): - # exit_profit_only and profit doesn't reach the offset - ignore sell signal - pass - elif self.use_exit_signal and not enter: + if self.use_exit_signal and not enter: if exit_: exit_signal = ExitType.EXIT_SIGNAL else: @@ -902,7 +899,11 @@ class IStrategy(ABC, HyperStrategyMixin): custom_reason = custom_reason[:CUSTOM_EXIT_MAX_LENGTH] else: custom_reason = None - if exit_signal in (ExitType.CUSTOM_EXIT, ExitType.EXIT_SIGNAL): + if ( + exit_signal == ExitType.CUSTOM_EXIT + or (exit_signal == ExitType.EXIT_SIGNAL + and (not self.exit_profit_only or current_profit > self.exit_profit_offset)) + ): logger.debug(f"{trade.pair} - Sell signal received. " f"exit_type=ExitType.{exit_signal.name}" + (f", custom_reason={custom_reason}" if custom_reason else "")) From 139b65835c54c41e3ca01b3a6ff61f235c9e0748 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Apr 2022 17:09:04 +0200 Subject: [PATCH 057/254] Only show long/short signals on telegram for non-spot markets --- freqtrade/rpc/rpc.py | 9 +++++++-- tests/rpc/test_rpc_telegram.py | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 8f3d57cf6..e151a1e07 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -223,6 +223,7 @@ class RPC: def _rpc_status_table(self, stake_currency: str, fiat_display_currency: str) -> Tuple[List, List, float]: trades: List[Trade] = Trade.get_open_trades() + nonspot = self._config.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT if not trades: raise RPCException('no active trade') else: @@ -237,7 +238,7 @@ class RPC: current_rate = NAN trade_profit = trade.calc_profit(current_rate) profit_str = f'{trade.calc_profit_ratio(current_rate):.2%}' - direction_str = 'S' if trade.is_short else 'L' + direction_str = ('S' if trade.is_short else 'L') if nonspot else '' if self._fiat_converter: fiat_profit = self._fiat_converter.convert_amount( trade_profit, @@ -267,7 +268,11 @@ class RPC: if self._fiat_converter: profitcol += " (" + fiat_display_currency + ")" - columns = ['ID L/S', 'Pair', 'Since', profitcol] + columns = [ + 'ID L/S' if nonspot else 'ID', + 'Pair', + 'Since', + profitcol] if self._config.get('position_adjustment_enable', False): columns.append('# Entries') return trades_list, columns, fiat_profit_sum diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index f104e7153..6d422ce11 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -398,8 +398,8 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ') assert int(fields[0]) == 1 - assert 'L' in fields[1] - assert 'ETH/BTC' in fields[2] + # assert 'L' in fields[1] + assert 'ETH/BTC' in fields[1] assert msg_mock.call_count == 1 From 114591048c9daa227b31b7990fab6be97e0093af Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Apr 2022 17:17:49 +0200 Subject: [PATCH 058/254] Always call custom_sell - also when there's a new enter signal --- docs/strategy-callbacks.md | 4 ++-- freqtrade/strategy/interface.py | 4 ++-- tests/test_freqtradebot.py | 8 +++++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 302ffd5fd..bd32f41c3 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -88,11 +88,11 @@ Allows to define custom exit signals, indicating that specified position should For example you could implement a 1:2 risk-reward ROI with `custom_exit()`. -Using custom_exit() signals in place of stoploss though *is not recommended*. It is a inferior method to using `custom_stoploss()` in this regard - which also allows you to keep the stoploss on exchange. +Using `custom_exit()` signals in place of stoploss though *is not recommended*. It is a inferior method to using `custom_stoploss()` in this regard - which also allows you to keep the stoploss on exchange. !!! Note Returning a (none-empty) `string` or `True` from this method is equal to setting exit signal on a candle at specified time. This method is not called when exit signal is set already, or if exit signals are disabled (`use_exit_signal=False`). `string` max length is 64 characters. Exceeding this limit will cause the message to be truncated to 64 characters. - `custom_exit()` will ignore `exit_profit_only`, and will always be called unless `use_exit_signal=False` or if there is an enter signal. + `custom_exit()` will ignore `exit_profit_only`, and will always be called unless `use_exit_signal=False`, even if there is a new enter signal. An example of how we can use different indicators depending on the current profit and also exit trades that were open longer than one day: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 1c53b2e3e..ebaa6568f 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -881,8 +881,8 @@ class IStrategy(ABC, HyperStrategyMixin): current_rate = rate current_profit = trade.calc_profit_ratio(current_rate) - if self.use_exit_signal and not enter: - if exit_: + if self.use_exit_signal: + if exit_ and not enter: exit_signal = ExitType.EXIT_SIGNAL else: trade_type = "exit_short" if trade.is_short else "sell" diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index e4066413e..3737c7c05 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3663,6 +3663,7 @@ def test_exit_profit_only( }) freqtrade = FreqtradeBot(default_conf_usdt) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) + freqtrade.strategy.custom_exit = MagicMock(return_value=None) if exit_type == ExitType.EXIT_SIGNAL.value: freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) else: @@ -3671,10 +3672,15 @@ def test_exit_profit_only( freqtrade.enter_positions() trade = Trade.query.first() - trade.is_short = is_short + assert trade.is_short == is_short oobj = Order.parse_from_ccxt_object(limit_order[eside], limit_order[eside]['symbol'], eside) trade.update_trade(oobj) freqtrade.wallets.update() + if profit_only: + assert freqtrade.handle_trade(trade) is False + # Custom-exit is called + freqtrade.strategy.custom_exit.call_count == 1 + patch_get_signal(freqtrade, enter_long=False, exit_short=is_short, exit_long=not is_short) assert freqtrade.handle_trade(trade) is handle_first From 9f9219675ff06a02fde10d7c18cad08957cca46b Mon Sep 17 00:00:00 2001 From: RafaelDorigo Date: Sat, 9 Apr 2022 19:58:58 +0200 Subject: [PATCH 059/254] Update strategy_migration.md --- docs/strategy_migration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/strategy_migration.md b/docs/strategy_migration.md index eb1729ba7..c53e10e6e 100644 --- a/docs/strategy_migration.md +++ b/docs/strategy_migration.md @@ -9,7 +9,7 @@ You can use the quick summary as checklist. Please refer to the detailed section ## Quick summary / migration checklist -Note : `force_exit`, `force_enter`, `emergency_exit` are changed to `force_exit`, `force_enter`, `emergency_exit` respectively. +Note : `forcesell`, `forcebuy`, `emergencysell` are changed to `force_exit`, `force_enter`, `emergency_exit` respectively. * Strategy methods: * [`populate_buy_trend()` -> `populate_entry_trend()`](#populate_buy_trend) From 09b41a6f8de114de3cb7378f487d6df2cd771da4 Mon Sep 17 00:00:00 2001 From: zolbayars Date: Sun, 10 Apr 2022 10:39:48 +0800 Subject: [PATCH 060/254] Docs: update plotting doc to show strategy option is mandatory --- docs/plotting.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/plotting.md b/docs/plotting.md index df988c578..6ae0c3f11 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -96,7 +96,7 @@ Strategy arguments: Example: ``` bash -freqtrade plot-dataframe -p BTC/ETH +freqtrade plot-dataframe -p BTC/ETH --strategy AwesomeStrategy ``` The `-p/--pairs` argument can be used to specify pairs you would like to plot. @@ -107,9 +107,6 @@ The `-p/--pairs` argument can be used to specify pairs you would like to plot. Specify custom indicators. Use `--indicators1` for the main plot and `--indicators2` for the subplot below (if values are in a different range than prices). -!!! Tip - You will almost certainly want to specify a custom strategy! This can be done by adding `-s Classname` / `--strategy ClassName` to the command. - ``` bash freqtrade plot-dataframe --strategy AwesomeStrategy -p BTC/ETH --indicators1 sma ema --indicators2 macd ``` From 850760bc00b68848cf929520a752d2767de20cfc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 9 Apr 2022 17:37:04 +0200 Subject: [PATCH 061/254] Remove migration from very old database (database without Orders table) --- freqtrade/persistence/migrations.py | 28 +++----- tests/test_persistence.py | 100 +++++++++++++++++++++------- 2 files changed, 85 insertions(+), 43 deletions(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 996af7341..a28683e04 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -3,6 +3,8 @@ from typing import List from sqlalchemy import inspect, text +from freqtrade.exceptions import OperationalException + logger = logging.getLogger(__name__) @@ -176,23 +178,6 @@ def migrate_trades_and_orders_table( set_sequence_ids(engine, order_id, trade_id) -def migrate_open_orders_to_trades(engine): - with engine.begin() as connection: - connection.execute(text(""" - insert into orders (ft_trade_id, ft_pair, order_id, ft_order_side, ft_is_open) - select id ft_trade_id, pair ft_pair, open_order_id, - case when close_rate_requested is null then 'buy' - else 'sell' end ft_order_side, 1 ft_is_open - from trades - where open_order_id is not null - union all - select id ft_trade_id, pair ft_pair, stoploss_order_id order_id, - 'stoploss' ft_order_side, 1 ft_is_open - from trades - where stoploss_order_id is not null - """)) - - def drop_orders_table(engine, table_back_name: str): # Drop and recreate orders table as backup # This drops foreign keys, too. @@ -210,7 +195,7 @@ def migrate_orders_table(engine, table_back_name: str, cols_order: List): # sqlite does not support literals for booleans with engine.begin() as connection: connection.execute(text(f""" - insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, + insert into orders (id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status, symbol, order_type, side, price, amount, filled, average, remaining, cost, order_date, order_filled_date, order_update_date, ft_fee_base) select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, @@ -252,6 +237,9 @@ def check_migrate(engine, decl_base, previous_tables) -> None: order_table_bak_name, cols_orders) if 'orders' not in previous_tables and 'trades' in previous_tables: - logger.info('Moving open orders to Orders table.') - migrate_open_orders_to_trades(engine) + raise OperationalException( + "Your database seems to be very old. " + "Please update to freqtrade 2022.3 to migrate this database or " + "start with a fresh database.") + set_sqlite_to_wal(engine) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index d30d33d3b..ecac561f8 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -1209,6 +1209,27 @@ def test_migrate_new(mocker, default_conf, fee, caplog): PRIMARY KEY (id), CHECK (is_open IN (0, 1)) );""" + create_table_order = """CREATE TABLE orders ( + id INTEGER NOT NULL, + ft_trade_id INTEGER, + ft_order_side VARCHAR(25) NOT NULL, + ft_pair VARCHAR(25) NOT NULL, + ft_is_open BOOLEAN NOT NULL, + order_id VARCHAR(255) NOT NULL, + status VARCHAR(255), + symbol VARCHAR(25), + order_type VARCHAR(50), + side VARCHAR(25), + price FLOAT, + amount FLOAT, + filled FLOAT, + remaining FLOAT, + cost FLOAT, + order_date DATETIME, + order_filled_date DATETIME, + order_update_date DATETIME, + PRIMARY KEY (id) + );""" insert_table_old = """INSERT INTO trades (exchange, pair, is_open, fee, open_rate, stake_amount, amount, open_date, stop_loss, initial_stop_loss, max_rate, ticker_interval, @@ -1222,15 +1243,66 @@ def test_migrate_new(mocker, default_conf, fee, caplog): stake=default_conf.get("stake_amount"), amount=amount ) + insert_orders = f""" + insert into orders ( + ft_trade_id, + ft_order_side, + ft_pair, + ft_is_open, + order_id, + status, + symbol, + order_type, + side, + price, + amount, + filled, + remaining, + cost) + values ( + 1, + 'buy', + 'ETC/BTC', + 0, + 'buy_order', + 'closed', + 'ETC/BTC', + 'limit', + 'buy', + 0.00258580, + {amount}, + {amount}, + 0, + {amount * 0.00258580} + ), + ( + 1, + 'stoploss', + 'ETC/BTC', + 0, + 'stop_order_id222', + 'closed', + 'ETC/BTC', + 'limit', + 'sell', + 0.00258580, + {amount}, + {amount}, + 0, + {amount * 0.00258580} + ) + """ engine = create_engine('sqlite://') mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine) # Create table using the old format with engine.begin() as connection: connection.execute(text(create_table_old)) + connection.execute(text(create_table_order)) connection.execute(text("create index ix_trades_is_open on trades(is_open)")) connection.execute(text("create index ix_trades_pair on trades(pair)")) connection.execute(text(insert_table_old)) + connection.execute(text(insert_orders)) # fake previous backup connection.execute(text("create table trades_bak as select * from trades")) @@ -1267,8 +1339,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert trade.open_trade_value == trade._calc_open_trade_value() assert trade.close_profit_abs is None - assert log_has("Moving open orders to Orders table.", caplog) - orders = Order.query.all() + orders = trade.orders assert len(orders) == 2 assert orders[0].order_id == 'buy_order' assert orders[0].ft_order_side == 'buy' @@ -1277,7 +1348,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog): assert orders[1].ft_order_side == 'stoploss' -def test_migrate_mid_state(mocker, default_conf, fee, caplog): +def test_migrate_too_old(mocker, default_conf, fee, caplog): """ Test Database migration (starting with new pairformat) """ @@ -1301,6 +1372,7 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog): PRIMARY KEY (id), CHECK (is_open IN (0, 1)) );""" + insert_table_old = """INSERT INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, stake_amount, amount, open_date) VALUES ('binance', 'ETC/BTC', 1, {fee}, {fee}, @@ -1319,26 +1391,8 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog): connection.execute(text(insert_table_old)) # Run init to test migration - init_db(default_conf['db_url'], default_conf['dry_run']) - - assert len(Trade.query.filter(Trade.id == 1).all()) == 1 - trade = Trade.query.filter(Trade.id == 1).first() - assert trade.fee_open == fee.return_value - assert trade.fee_close == fee.return_value - assert trade.open_rate_requested is None - assert trade.close_rate_requested is None - assert trade.is_open == 1 - assert trade.amount == amount - assert trade.stake_amount == default_conf.get("stake_amount") - assert trade.pair == "ETC/BTC" - assert trade.exchange == "binance" - assert trade.max_rate == 0.0 - assert trade.stop_loss == 0.0 - assert trade.initial_stop_loss == 0.0 - assert trade.open_trade_value == trade._calc_open_trade_value() - assert log_has("trying trades_bak0", caplog) - assert log_has("Running database migration for trades - backup: trades_bak0, orders_bak0", - caplog) + with pytest.raises(OperationalException, match=r'Your database seems to be very old'): + init_db(default_conf['db_url'], default_conf['dry_run']) def test_migrate_get_last_sequence_ids(): From ffff45e76bb1e647f17f5f8711664ba4985f2de2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 10 Apr 2022 08:37:35 +0200 Subject: [PATCH 062/254] simplify exit message --- freqtrade/strategy/interface.py | 3 +-- tests/strategy/test_interface.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index ebaa6568f..ba2eb9636 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -885,7 +885,6 @@ class IStrategy(ABC, HyperStrategyMixin): if exit_ and not enter: exit_signal = ExitType.EXIT_SIGNAL else: - trade_type = "exit_short" if trade.is_short else "sell" custom_reason = strategy_safe_wrapper(self.custom_exit, default_retval=False)( pair=trade.pair, trade=trade, current_time=current_time, current_rate=current_rate, current_profit=current_profit) @@ -893,7 +892,7 @@ class IStrategy(ABC, HyperStrategyMixin): exit_signal = ExitType.CUSTOM_EXIT if isinstance(custom_reason, str): if len(custom_reason) > CUSTOM_EXIT_MAX_LENGTH: - logger.warning(f'Custom {trade_type} reason returned from ' + logger.warning(f'Custom exit reason returned from ' f'custom_exit is too long and was trimmed' f'to {CUSTOM_EXIT_MAX_LENGTH} characters.') custom_reason = custom_reason[:CUSTOM_EXIT_MAX_LENGTH] diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 44a17ac02..a86d69135 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -523,7 +523,7 @@ def test_custom_exit(default_conf, fee, caplog) -> None: assert res.exit_type == ExitType.CUSTOM_EXIT assert res.exit_flag is True assert res.exit_reason == 'h' * 64 - assert log_has_re('Custom sell reason returned from custom_exit is too long.*', caplog) + assert log_has_re('Custom exit reason returned from custom_exit is too long.*', caplog) @pytest.mark.parametrize('side', TRADE_SIDES) From 282804463c7d55850036d5a411c7b63e2d8b1b4d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 10 Apr 2022 09:07:51 +0200 Subject: [PATCH 063/254] Add Documentation for /forceexit without parameter --- docs/telegram-usage.md | 7 +++++-- freqtrade/rpc/telegram.py | 7 +++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index a5709059a..27f5f91b6 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -275,9 +275,12 @@ The relative profit of `1.2%` is the average profit per trade. The relative profit of `15.2 Σ%` is be based on the starting capital - so in this case, the starting capital was `0.00485701 * 1.152 = 0.00738 BTC`. Starting capital is either taken from the `available_capital` setting, or calculated by using current wallet size - profits. -### /forcesell +### /forceexit -> **BINANCE:** Selling BTC/LTC with limit `0.01650000 (profit: ~-4.07%, -0.00008168)` +> **BINANCE:** Exiting BTC/LTC with limit `0.01650000 (profit: ~-4.07%, -0.00008168)` + +!!! Tip + You can get a list of all open trades by calling `/forceexit` without parameter, which will show a list of buttons to simply exit a trade. ### /forcelong [rate] | /forceshort [rate] diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index fb86d0481..704dca972 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -103,7 +103,6 @@ class Telegram(RPCHandler): ['/count', '/start', '/stop', '/help'] ] # do not allow commands with mandatory arguments and critical cmds - # like /forcesell and /forcebuy # TODO: DRY! - its not good to list all valid cmds here. But otherwise # this needs refactoring of the whole telegram module (same # problem in _help()). @@ -115,7 +114,8 @@ class Telegram(RPCHandler): r'/stopbuy$', r'/reload_config$', r'/show_config$', r'/logs$', r'/whitelist$', r'/blacklist$', r'/bl_delete$', r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', - r'/forcebuy$', r'/forcelong$', r'/forceshort$', r'/forcesell$', + r'/forcebuy$', r'/forcelong$', r'/forceshort$', + r'/forcesell$', r'/forceexit$', r'/edge$', r'/health$', r'/help$', r'/version$'] # Create keys for generation valid_keys_print = [k.replace('$', '') for k in valid_keys] @@ -929,7 +929,7 @@ class Telegram(RPCHandler): @authorized_only def _force_exit(self, update: Update, context: CallbackContext) -> None: """ - Handler for /forcesell . + Handler for /forceexit . Sells the given trade at current price :param bot: telegram bot :param update: message update @@ -1019,7 +1019,6 @@ class Telegram(RPCHandler): :param update: message update :return: None """ - if context.args: pair = context.args[0] price = float(context.args[1]) if len(context.args) > 1 else None From 95f69a8c3bf7ceb34468288ddc3e9dee9f5ea56f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 10 Apr 2022 09:33:29 +0200 Subject: [PATCH 064/254] Remove some outdated TODO's --- freqtrade/data/btanalysis.py | 9 +-------- freqtrade/rpc/telegram.py | 1 - 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index c8654cfda..db7ef66fc 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -193,14 +193,7 @@ def find_existing_backtest_stats(dirname: Union[Path, str], run_ids: Dict[str, s continue if min_backtest_date is not None: - try: - backtest_date = strategy_metadata['backtest_start_time'] - except KeyError: - # TODO: this can be removed starting from feb 2022 - # The metadata-file without start_time was only available in develop - # and was never included in an official release. - # Older metadata format without backtest time, too old to consider. - return results + backtest_date = strategy_metadata['backtest_start_time'] backtest_date = datetime.fromtimestamp(backtest_date, tz=timezone.utc) if backtest_date < min_backtest_date: # Do not use a cached result for this strategy as first result is too old. diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index ce43dfb4f..c702fb6b8 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1414,7 +1414,6 @@ class Telegram(RPCHandler): "*/start:* `Starts the trader`\n" "*/stop:* Stops the trader\n" "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" - # TODO: forceenter forceshort forcelong missing "*/forceexit |all:* `Instantly exits the given trade or all trades, " "regardless of profit`\n" "*/fe |all:* `Alias to /forceexit`" From cd2e49bb60d8cea4d51afc7dd65ac28e74f20f37 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 10 Apr 2022 09:46:23 +0200 Subject: [PATCH 065/254] Simplify downloading futures data code --- freqtrade/data/history/history_utils.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index 515a345f1..8560fd29e 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -179,6 +179,7 @@ def _download_pair_history(pair: str, *, data_handler: IDataHandler = None, timerange: Optional[TimeRange] = None, candle_type: CandleType, + erase: bool = False, ) -> bool: """ Download latest candles from the exchange for the pair and timeframe passed in parameters @@ -192,11 +193,16 @@ def _download_pair_history(pair: str, *, :param timeframe: Timeframe (e.g "5m") :param timerange: range of time to download :param candle_type: Any of the enum CandleType (must match trading mode!) + :param erase: Erase existing data :return: bool with success state """ data_handler = get_datahandler(datadir, data_handler=data_handler) try: + if erase: + if data_handler.ohlcv_purge(pair, timeframe, candle_type=candle_type): + logger.info(f'Deleting existing data for pair {pair}, {timeframe}, {candle_type}.') + logger.info( f'Download history data for pair: "{pair}" ({process}), timeframe: {timeframe}, ' f'candle type: {candle_type} and store in {datadir}.' @@ -267,35 +273,28 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes continue for timeframe in timeframes: - if erase: - if data_handler.ohlcv_purge(pair, timeframe, candle_type=candle_type): - logger.info(f'Deleting existing data for pair {pair}, interval {timeframe}.') - logger.info(f'Downloading pair {pair}, interval {timeframe}.') process = f'{idx}/{len(pairs)}' _download_pair_history(pair=pair, process=process, datadir=datadir, exchange=exchange, timerange=timerange, data_handler=data_handler, timeframe=str(timeframe), new_pairs_days=new_pairs_days, - candle_type=candle_type) + candle_type=candle_type, + erase=erase) if trading_mode == 'futures': # Predefined candletype (and timeframe) depending on exchange # Downloads what is necessary to backtest based on futures data. - timeframe = exchange._ft_has['mark_ohlcv_timeframe'] + tf_mark = exchange._ft_has['mark_ohlcv_timeframe'] fr_candle_type = CandleType.from_string(exchange._ft_has['mark_ohlcv_price']) # All exchanges need FundingRate for futures trading. # The timeframe is aligned to the mark-price timeframe. for funding_candle_type in (CandleType.FUNDING_RATE, fr_candle_type): - # TODO: this could be in most parts to the above. - if erase: - if data_handler.ohlcv_purge(pair, timeframe, candle_type=funding_candle_type): - logger.info( - f'Deleting existing data for pair {pair}, interval {timeframe}.') _download_pair_history(pair=pair, process=process, datadir=datadir, exchange=exchange, timerange=timerange, data_handler=data_handler, - timeframe=str(timeframe), new_pairs_days=new_pairs_days, - candle_type=funding_candle_type) + timeframe=str(tf_mark), new_pairs_days=new_pairs_days, + candle_type=funding_candle_type, + erase=erase) return pairs_not_available From 9556af1e6c82a7a739788967804794349a98fe1f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 10 Apr 2022 10:14:34 +0200 Subject: [PATCH 066/254] Improve documentation for config imports --- docs/configuration.md | 30 ++++++++++++++++++++++++++ freqtrade/configuration/load_config.py | 2 +- tests/test_configuration.py | 5 +++-- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 0c89bbbdd..369c4e2dd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -80,6 +80,36 @@ This is similar to using multiple `--config` parameters, but simpler in usage as This is equivalent to the example above - but `config-private.json` is specified as cli argument. +??? Note "config collision handling" + If the same configuration setting takes place in both `config.json` and `config-import.json`, then the parent configuration wins. + In the below case, `max_open_trades` would be 3 after the merging - as the reusable "import" configuration has this key overwritten. + + ``` json title="user_data/config.json" + { + "max_open_trades": 3, + "stake_currency": "USDT", + "add_config_files": [ + "config-import.json" + ] + } + ``` + + ``` json title="user_data/config-import.json" + { + "max_open_trades": 10, + "stake_amount": "unlimited", + } + ``` + + Resulting combined configuration: + + ``` json title="Result" + { + "max_open_trades": 10, + "stake_currency": "USDT", + "stake_amount": "unlimited" + } + ``` ## Configuration parameters diff --git a/freqtrade/configuration/load_config.py b/freqtrade/configuration/load_config.py index 32c2ae0d9..4ef531c7a 100644 --- a/freqtrade/configuration/load_config.py +++ b/freqtrade/configuration/load_config.py @@ -103,7 +103,7 @@ def load_from_files(files: List[str], base_path: Path = None, level: int = 0) -> config_sub = load_from_files( config_tmp['add_config_files'], file.resolve().parent, level + 1) files_loaded.extend(config_sub.get('config_files', [])) - deep_merge_dicts(config_sub, config_tmp) + config_tmp = deep_merge_dicts(config_tmp, config_sub) files_loaded.insert(0, str(file)) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 957468b86..db87c405f 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -219,8 +219,9 @@ def test_from_recursive_files(testdatadir) -> None: assert conf['entry_pricing'] assert conf['entry_pricing']['price_side'] == "same" assert conf['exit_pricing'] - # The other key comes from pricing2, which is imported by pricing.json - assert conf['exit_pricing']['price_side'] == "other" + # The other key comes from pricing2, which is imported by pricing.json. + # pricing.json is a level higher, therefore wins. + assert conf['exit_pricing']['price_side'] == "same" assert len(conf['config_files']) == 4 assert 'testconfig.json' in conf['config_files'][0] From 77c840c2a445fe488f8d8f9a535e556ca523cfd8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 10 Apr 2022 15:10:13 +0200 Subject: [PATCH 067/254] Fix syntax-error in exit_reason migration --- freqtrade/persistence/migrations.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index a28683e04..f020f990c 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -159,10 +159,10 @@ def migrate_trades_and_orders_table( {initial_stop_loss_pct} initial_stop_loss_pct, {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, {max_rate} max_rate, {min_rate} min_rate, - case when {exit_reason} == 'sell_signal' then 'exit_signal' - when {exit_reason} == 'custom_sell' then 'custom_exit' - when {exit_reason} == 'force_sell' then 'force_exit' - when {exit_reason} == 'emergency_sell' then 'emergency_exit' + case when {exit_reason} = 'sell_signal' then 'exit_signal' + when {exit_reason} = 'custom_sell' then 'custom_exit' + when {exit_reason} = 'force_sell' then 'force_exit' + when {exit_reason} = 'emergency_sell' then 'emergency_exit' else {exit_reason} end exit_reason, {exit_order_status} exit_order_status, From 68fe7476c9fbce9aad3d8d704d92f39a898a69ee Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 10 Apr 2022 15:56:29 +0200 Subject: [PATCH 068/254] Update more terminology to forceexit --- freqtrade/optimize/backtesting.py | 2 +- freqtrade/rpc/api_server/api_v1.py | 2 +- freqtrade/rpc/rpc.py | 8 ++++---- freqtrade/rpc/telegram.py | 2 +- scripts/rest_client.py | 6 +++--- tests/optimize/test_backtest_detail.py | 8 ++++---- tests/rpc/test_rpc_apiserver.py | 8 ++++---- tests/rpc/test_rpc_telegram.py | 16 ++++++++-------- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 438337669..cbb220e45 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -417,7 +417,7 @@ class Backtesting: roi_entry, roi = self.strategy.min_roi_reached_entry(trade_dur) if roi is not None and roi_entry is not None: if roi == -1 and roi_entry % self.timeframe_min == 0: - # When forceselling with ROI=-1, the roi time will always be equal to trade_dur. + # When force_exiting with ROI=-1, the roi time will always be equal to trade_dur. # If that entry is a multiple of the timeframe (so on candle open) # - we'll use open instead of close return row[OPEN_IDX] diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 69338d665..d96154824 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -157,7 +157,7 @@ def force_entry(payload: ForceEnterPayload, rpc: RPC = Depends(get_rpc)): # /forcesell is deprecated with short addition. use /forceexit instead @router.post('/forceexit', response_model=ResultMsg, tags=['trading']) @router.post('/forcesell', response_model=ResultMsg, tags=['trading']) -def forcesell(payload: ForceExitPayload, rpc: RPC = Depends(get_rpc)): +def forceexit(payload: ForceExitPayload, rpc: RPC = Depends(get_rpc)): ordertype = payload.ordertype.value if payload.ordertype else None return rpc._rpc_force_exit(payload.tradeid, ordertype) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index f76992796..12adc34d1 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -690,10 +690,10 @@ class RPC: def _rpc_force_exit(self, trade_id: str, ordertype: Optional[str] = None) -> Dict[str, str]: """ - Handler for forcesell . + Handler for forceexit . Sells the given trade at current price """ - def _exec_forcesell(trade: Trade) -> None: + def _exec_force_exit(trade: Trade) -> None: # Check if there is there is an open order fully_canceled = False if trade.open_order_id: @@ -726,7 +726,7 @@ class RPC: if trade_id == 'all': # Execute sell for all open orders for trade in Trade.get_open_trades(): - _exec_forcesell(trade) + _exec_force_exit(trade) Trade.commit() self._freqtrade.wallets.update() return {'result': 'Created sell orders for all open trades.'} @@ -739,7 +739,7 @@ class RPC: logger.warning('force_exit: Invalid argument received') raise RPCException('invalid argument') - _exec_forcesell(trade) + _exec_force_exit(trade) Trade.commit() self._freqtrade.wallets.update() return {'result': f'Created sell order for trade {trade_id}.'} diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index c702fb6b8..b152b722c 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -274,7 +274,7 @@ class Telegram(RPCHandler): else "") # Check if all sell properties are available. - # This might not be the case if the message origin is triggered by /forcesell + # This might not be the case if the message origin is triggered by /forceexit if (all(prop in msg for prop in ['gain', 'fiat_currency', 'stake_currency']) and self._rpc._fiat_converter): msg['profit_fiat'] = self._rpc._fiat_converter.convert_amount( diff --git a/scripts/rest_client.py b/scripts/rest_client.py index 9c5f820b9..ecbb65253 100755 --- a/scripts/rest_client.py +++ b/scripts/rest_client.py @@ -275,14 +275,14 @@ class FtRestClient(): } return self._post("force_enter", data=data) - def forcesell(self, tradeid): - """Force-sell a trade. + def forceexit(self, tradeid): + """Force-exit a trade. :param tradeid: Id of the trade (can be received via status command) :return: json object """ - return self._post("forcesell", data={"tradeid": tradeid}) + return self._post("forceexit", data={"tradeid": tradeid}) def strategies(self): """Lists available strategies diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index fca204b52..ea13de4c8 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -251,7 +251,7 @@ tc15 = BTContainer(data=[ BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=2, close_tick=2)] ) -# Test 16: Buy, hold for 65 min, then forcesell using roi=-1 +# Test 16: Buy, hold for 65 min, then forceexit using roi=-1 # Causes negative profit even though sell-reason is ROI. # stop-loss: 10%, ROI: 10% (should not apply), -100% after 65 minutes (limits trade duration) tc16 = BTContainer(data=[ @@ -259,14 +259,14 @@ tc16 = BTContainer(data=[ [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 4987, 5300, 4950, 5050, 6172, 0, 0], - [3, 4975, 5000, 4940, 4962, 6172, 0, 0], # ForceSell on ROI (roi=-1) + [3, 4975, 5000, 4940, 4962, 6172, 0, 0], # Forceexit on ROI (roi=-1) [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "65": -1}, profit_perc=-0.012, trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)] ) -# Test 17: Buy, hold for 120 mins, then forcesell using roi=-1 +# Test 17: Buy, hold for 120 mins, then forceexit using roi=-1 # Causes negative profit even though sell-reason is ROI. # stop-loss: 10%, ROI: 10% (should not apply), -100% after 100 minutes (limits trade duration) # Uses open as sell-rate (special case) - since the roi-time is a multiple of the timeframe. @@ -275,7 +275,7 @@ tc17 = BTContainer(data=[ [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 4987, 5300, 4950, 5050, 6172, 0, 0], - [3, 4980, 5000, 4940, 4962, 6172, 0, 0], # ForceSell on ROI (roi=-1) + [3, 4980, 5000, 4940, 4962, 6172, 0, 0], # Forceexit on ROI (roi=-1) [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "120": -1}, profit_perc=-0.004, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 76cef0df0..54bf07dc2 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1182,7 +1182,7 @@ def test_api_force_entry(botclient, mocker, fee, endpoint): } -def test_api_forcesell(botclient, mocker, ticker, fee, markets): +def test_api_forceexit(botclient, mocker, ticker, fee, markets): ftbot, client = botclient mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -1194,15 +1194,15 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets): ) patch_get_signal(ftbot) - rc = client_post(client, f"{BASE_URI}/forcesell", + rc = client_post(client, f"{BASE_URI}/forceexit", data='{"tradeid": "1"}') assert_response(rc, 502) - assert rc.json() == {"error": "Error querying /api/v1/forcesell: invalid argument"} + assert rc.json() == {"error": "Error querying /api/v1/forceexit: invalid argument"} Trade.query.session.rollback() ftbot.enter_positions() - rc = client_post(client, f"{BASE_URI}/forcesell", + rc = client_post(client, f"{BASE_URI}/forceexit", data='{"tradeid": "1"}') assert_response(rc) assert rc.json() == {'result': 'Created sell order for trade 1.'} diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 3fd4d76fb..2bc4fc5c3 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1005,7 +1005,7 @@ def test_reload_config_handle(default_conf, update, mocker) -> None: assert 'Reloading config' in msg_mock.call_args_list[0][0][0] -def test_telegram_forcesell_handle(default_conf, update, ticker, fee, +def test_telegram_forceexit_handle(default_conf, update, ticker, fee, ticker_sell_up, mocker) -> None: mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) msg_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) @@ -1033,7 +1033,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee, # Increase the price and sell it mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', ticker_sell_up) - # /forcesell 1 + # /forceexit 1 context = MagicMock() context.args = ["1"] telegram._force_exit(update=update, context=context) @@ -1101,7 +1101,7 @@ def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee, trade = Trade.query.first() assert trade - # /forcesell 1 + # /forceexit 1 context = MagicMock() context.args = ["1"] telegram._force_exit(update=update, context=context) @@ -1137,7 +1137,7 @@ def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee, } == last_msg -def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None: +def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -> None: patch_exchange(mocker) mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) @@ -1160,7 +1160,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None freqtradebot.enter_positions() msg_mock.reset_mock() - # /forcesell all + # /forceexit all context = MagicMock() context.args = ["all"] telegram._force_exit(update=update, context=context) @@ -1196,7 +1196,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None } == msg -def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: +def test_forceexit_handle_invalid(default_conf, update, mocker) -> None: mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) @@ -1205,7 +1205,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: # Trader is not running freqtradebot.state = State.STOPPED - # /forcesell 1 + # /forceexit 1 context = MagicMock() context.args = ["1"] telegram._force_exit(update=update, context=context) @@ -1215,7 +1215,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None: # Invalid argument msg_mock.reset_mock() freqtradebot.state = State.RUNNING - # /forcesell 123456 + # /forceexit 123456 context = MagicMock() context.args = ["123456"] telegram._force_exit(update=update, context=context) From 47a6ef4f00707e57fac348035d6092c9bd5d7b71 Mon Sep 17 00:00:00 2001 From: Nicolas Papp Date: Sun, 10 Apr 2022 12:53:47 -0300 Subject: [PATCH 069/254] Max relative drawdown --- docs/hyperopt.md | 7 ++- freqtrade/constants.py | 3 +- freqtrade/data/btanalysis.py | 23 ++++++---- .../hyperopt_loss_max_drawdown_relative.py | 45 +++++++++++++++++++ freqtrade/optimize/optimize_reports.py | 9 +++- 5 files changed, 73 insertions(+), 14 deletions(-) create mode 100644 freqtrade/optimize/hyperopt_loss_max_drawdown_relative.py diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 3f613a208..bab062fad 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -116,7 +116,9 @@ optional arguments: ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss, SharpeHyperOptLossDaily, SortinoHyperOptLoss, SortinoHyperOptLossDaily, - CalmarHyperOptLoss, MaxDrawDownHyperOptLoss, ProfitDrawDownHyperOptLoss + CalmarHyperOptLoss, MaxDrawDownHyperOptLoss, + MaxDrawDownRelativeHyperOptLoss, + ProfitDrawDownHyperOptLoss --disable-param-export Disable automatic hyperopt parameter export. --ignore-missing-spaces, --ignore-unparameterized-spaces @@ -563,7 +565,8 @@ Currently, the following loss functions are builtin: * `SharpeHyperOptLossDaily` - optimizes Sharpe Ratio calculated on **daily** trade returns relative to standard deviation. * `SortinoHyperOptLoss` - optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation. * `SortinoHyperOptLossDaily` - optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation. -* `MaxDrawDownHyperOptLoss` - Optimizes Maximum drawdown. +* `MaxDrawDownHyperOptLoss` - Optimizes Maximum absolute drawdown. +* `MaxDrawDownRelativeHyperOptLoss` - Similar as the above, but also optimizes Maximum relative drawdown. * `CalmarHyperOptLoss` - Optimizes Calmar Ratio calculated on trade returns relative to max drawdown. * `ProfitDrawDownHyperOptLoss` - Optimizes by max Profit & min Drawdown objective. `DRAWDOWN_MULT` variable within the hyperoptloss file can be adjusted to be stricter or more flexible on drawdown purposes. diff --git a/freqtrade/constants.py b/freqtrade/constants.py index a06e2771f..e2d4d9a13 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -28,7 +28,8 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', 'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily', 'CalmarHyperOptLoss', - 'MaxDrawDownHyperOptLoss', 'ProfitDrawDownHyperOptLoss'] + 'MaxDrawDownHyperOptLoss', 'MaxDrawDownRelativeHyperOptLoss', + 'ProfitDrawDownHyperOptLoss'] AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'AgeFilter', 'OffsetFilter', 'PerformanceFilter', 'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter', diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index c8654cfda..66d7b4ad5 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -441,18 +441,22 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str, return df -def _calc_drawdown_series(profit_results: pd.DataFrame, *, date_col: str, value_col: str - ) -> pd.DataFrame: +def _calc_drawdown_series(profit_results: pd.DataFrame, *, date_col: str, value_col: str, + starting_balance : Optional[float] = 0.0) -> pd.DataFrame: max_drawdown_df = pd.DataFrame() max_drawdown_df['cumulative'] = profit_results[value_col].cumsum() max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax() max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value'] max_drawdown_df['date'] = profit_results.loc[:, date_col] + if starting_balance: + cumulative_balance = starting_balance + max_drawdown_df['cumulative'] + max_balance = starting_balance + max_drawdown_df['high_value'] + max_drawdown_df['drawdown_relative'] = ((max_balance - cumulative_balance) / max_balance) return max_drawdown_df def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date', - value_col: str = 'profit_ratio' + value_col: str = 'profit_ratio', starting_balance : Optional[float] = 0.0 ): """ Calculate max drawdown and the corresponding close dates @@ -466,13 +470,14 @@ def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date', if len(trades) == 0: raise ValueError("Trade dataframe empty.") profit_results = trades.sort_values(date_col).reset_index(drop=True) - max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col) + max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col, starting_balance=starting_balance) return max_drawdown_df def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date', - value_col: str = 'profit_abs', starting_balance: float = 0 + value_col: str = 'profit_abs', starting_balance: float = 0, + relative: bool = False ) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float, float]: """ Calculate max drawdown and the corresponding close dates @@ -488,9 +493,9 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date' if len(trades) == 0: raise ValueError("Trade dataframe empty.") profit_results = trades.sort_values(date_col).reset_index(drop=True) - max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col) + max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col, starting_balance=starting_balance) - idxmin = max_drawdown_df['drawdown'].idxmin() + idxmin = max_drawdown_df['drawdown_relative'].idxmax() if relative else max_drawdown_df['drawdown'].idxmin() if idxmin == 0: raise ValueError("No losing trade, therefore no drawdown.") high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(), date_col] @@ -499,8 +504,8 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date' ['high_value'].idxmax(), 'cumulative'] low_val = max_drawdown_df.loc[idxmin, 'cumulative'] max_drawdown_rel = 0.0 - if high_val + starting_balance != 0: - max_drawdown_rel = (high_val - low_val) / (high_val + starting_balance) + if starting_balance != 0: + max_drawdown_rel = max_drawdown_df.loc[idxmin, 'drawdown_relative'] return ( abs(min(max_drawdown_df['drawdown'])), diff --git a/freqtrade/optimize/hyperopt_loss_max_drawdown_relative.py b/freqtrade/optimize/hyperopt_loss_max_drawdown_relative.py new file mode 100644 index 000000000..c4dd843b8 --- /dev/null +++ b/freqtrade/optimize/hyperopt_loss_max_drawdown_relative.py @@ -0,0 +1,45 @@ +""" +MaxDrawDownRelativeHyperOptLoss + +This module defines the alternative HyperOptLoss class which can be used for +Hyperoptimization. +""" +from datetime import datetime +from typing import Dict + +from pandas import DataFrame + +from freqtrade.data.btanalysis import calculate_underwater, calculate_max_drawdown +from freqtrade.optimize.hyperopt import IHyperOptLoss + + +class MaxDrawDownRelativeHyperOptLoss(IHyperOptLoss): + + """ + Defines the loss function for hyperopt. + + This implementation optimizes for max draw down and profit + Less max drawdown more profit -> Lower return value + """ + + @staticmethod + def hyperopt_loss_function(results: DataFrame, config: Dict, + *args, **kwargs) -> float: + + """ + Objective function. + + Uses profit ratio weighted max_drawdown when drawdown is available. + Otherwise directly optimizes profit ratio. + """ + total_profit = results['profit_abs'].sum() + try: + drawdown_df = calculate_underwater(results, value_col='profit_abs', starting_balance=config['available_capital']) + max_drawdown = abs(min(drawdown_df['drawdown'])) + relative_drawdown = max(drawdown_df['drawdown_relative']) + if max_drawdown == 0: + return -total_profit + return -total_profit / max_drawdown / relative_drawdown + except (Exception, ValueError): + return -total_profit + diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 1966c7ad1..2bf09d71b 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -474,9 +474,12 @@ def generate_strategy_stats(pairlist: List[str], (drawdown_abs, drawdown_start, drawdown_end, high_val, low_val, max_drawdown) = calculate_max_drawdown( results, value_col='profit_abs', starting_balance=start_balance) + (_, _, _, _, _, max_relative_drawdown) = calculate_max_drawdown( + results, value_col='profit_abs', starting_balance=start_balance, relative=True) strat_stats.update({ 'max_drawdown': max_drawdown_legacy, # Deprecated - do not use 'max_drawdown_account': max_drawdown, + 'max_relative_drawdown': max_relative_drawdown, 'max_drawdown_abs': drawdown_abs, 'drawdown_start': drawdown_start.strftime(DATETIME_PRINT_FORMAT), 'drawdown_start_ts': drawdown_start.timestamp() * 1000, @@ -497,6 +500,7 @@ def generate_strategy_stats(pairlist: List[str], strat_stats.update({ 'max_drawdown': 0.0, 'max_drawdown_account': 0.0, + 'max_relative_drawdown': 0.0, 'max_drawdown_abs': 0.0, 'max_drawdown_low': 0.0, 'max_drawdown_high': 0.0, @@ -760,10 +764,11 @@ def text_table_add_metrics(strat_results: Dict) -> str: strat_results['stake_currency'])), # Compatibility to show old hyperopt results - ('Drawdown (Account)', f"{strat_results['max_drawdown_account']:.2%}") + ('Max % of account underwater', f"{strat_results['max_relative_drawdown']:.2%}"), + ('Absolute Drawdown (Account)', f"{strat_results['max_drawdown_account']:.2%}") if 'max_drawdown_account' in strat_results else ( 'Drawdown', f"{strat_results['max_drawdown']:.2%}"), - ('Drawdown', round_coin_value(strat_results['max_drawdown_abs'], + ('Absolute Drawdown', round_coin_value(strat_results['max_drawdown_abs'], strat_results['stake_currency'])), ('Drawdown high', round_coin_value(strat_results['max_drawdown_high'], strat_results['stake_currency'])), From d5ce868f1aa1d99c0f7d936cc88161611dc8aa4c Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 10 Apr 2022 18:44:33 -0600 Subject: [PATCH 070/254] removed 1 letter alias for recursive-strategy-folder --- freqtrade/commands/cli_options.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 2ed42b299..095aad6c3 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -83,7 +83,7 @@ AVAILABLE_CLI_OPTIONS = { action='store_true', ), "recursive_strategy_search": Arg( - '-r', '--recursive_strategy_search', + '--recursive_strategy_search', help='Recursively search for a strategy in the strategies folder.', metavar='store_true', ), From c876d42e369baae4d86d7abd2db035454a6904b9 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 10 Apr 2022 18:50:51 -0600 Subject: [PATCH 071/254] safe check for recursive_strategy_search in strategy_resolver --- freqtrade/resolvers/strategy_resolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 44f02e232..8a22dbd65 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -167,7 +167,7 @@ class StrategyResolver(IResolver): :param extra_dir: additional directory to search for the given strategy :return: Strategy instance or None """ - if config['recursive_strategy_search']: + if 'recursive_strategy_search' in config and config['recursive_strategy_search']: extra_dirs: List[str] = [ path[0] for path in walk(f"{config['user_data_dir']}/{USERPATH_STRATEGIES}") ] # sub-directories From 64e6729ae94b0c6027dc45be48b6185f7b1dbf6a Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 10 Apr 2022 18:56:28 -0600 Subject: [PATCH 072/254] docs for recursive_strategy_search --- docs/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration.md b/docs/configuration.md index 2cb5dfa93..9c1b3718c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -121,6 +121,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict | `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** Dict | `custom_price_max_distance_ratio` | Configure maximum distance ratio between current and custom entry or exit price.
*Defaults to `0.02` 2%).*
**Datatype:** Positive float +| `recursive_strategy_search` | Set to `true` to recursively search sub-directories inside `user_data/strategies` for a strategy.
**Datatype:** Boolean | `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
**Datatype:** String | `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.
**Datatype:** Boolean | `exchange.key` | API key to use for the exchange. Only required when you are in production mode.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String From 84fca32ed9fbfe9e340af30387e6d731dfadd546 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 03:01:29 +0000 Subject: [PATCH 073/254] Bump mkdocs-material from 8.2.8 to 8.2.9 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.2.8 to 8.2.9. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/8.2.8...8.2.9) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 1f7db75c5..8d03a38c3 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.3.0 -mkdocs-material==8.2.8 +mkdocs-material==8.2.9 mdx_truly_sane_lists==1.2 pymdown-extensions==9.3 jinja2==3.1.1 From 72fd4bf3373879323fd81489e96f46a08fc8a9a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 03:01:36 +0000 Subject: [PATCH 074/254] Bump sqlalchemy from 1.4.34 to 1.4.35 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.4.34 to 1.4.35. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: sqlalchemy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index eee88001d..0fad682c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ ccxt==1.77.98 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.2 aiohttp==3.8.1 -SQLAlchemy==1.4.34 +SQLAlchemy==1.4.35 python-telegram-bot==13.11 arrow==1.2.2 cachetools==4.2.2 From 739cd773c33f6a51b8e399e434b242f9ec5068e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 03:01:39 +0000 Subject: [PATCH 075/254] Bump plotly from 5.6.0 to 5.7.0 Bumps [plotly](https://github.com/plotly/plotly.py) from 5.6.0 to 5.7.0. - [Release notes](https://github.com/plotly/plotly.py/releases) - [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md) - [Commits](https://github.com/plotly/plotly.py/compare/v5.6.0...v5.7.0) --- updated-dependencies: - dependency-name: plotly dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index bb2132f87..9eb6a10a3 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,5 +1,5 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==5.6.0 +plotly==5.7.0 From 5edae71d2884ab65cbd5dc57318107ce52c7f92b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 03:01:53 +0000 Subject: [PATCH 076/254] Bump prompt-toolkit from 3.0.28 to 3.0.29 Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.28 to 3.0.29. - [Release notes](https://github.com/prompt-toolkit/python-prompt-toolkit/releases) - [Changelog](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/CHANGELOG) - [Commits](https://github.com/prompt-toolkit/python-prompt-toolkit/compare/3.0.28...3.0.29) --- updated-dependencies: - dependency-name: prompt-toolkit dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index eee88001d..854cefc3d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,7 +41,7 @@ psutil==5.9.0 colorama==0.4.4 # Building config files interactively questionary==1.10.0 -prompt-toolkit==3.0.28 +prompt-toolkit==3.0.29 # Extensions to datetime library python-dateutil==2.8.2 From c93bed5e2bcbf72802d6cf864c4a5e63d36411a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 04:28:17 +0000 Subject: [PATCH 077/254] Bump ccxt from 1.77.98 to 1.78.62 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.77.98 to 1.78.62. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.77.98...1.78.62) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 0fad682c9..7a6e04923 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.22.3 pandas==1.4.2 pandas-ta==0.3.14b -ccxt==1.77.98 +ccxt==1.78.62 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.2 aiohttp==3.8.1 From 43779232e143a725eb8db0a4e21642562541a63a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Apr 2022 07:13:31 +0200 Subject: [PATCH 078/254] Inlcude docs requirements in dev dependencies developers should also be able to render the docs without further action. --- requirements-dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-dev.txt b/requirements-dev.txt index 20fd420bd..03bb5e72d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,6 +2,7 @@ -r requirements.txt -r requirements-plot.txt -r requirements-hyperopt.txt +-r docs/requirements-docs.txt coveralls==3.3.1 flake8==4.0.1 From 1084787a385e1befc959aa294cc3253bba1201b1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Apr 2022 17:48:39 +0200 Subject: [PATCH 079/254] Add note for Update releases --- docs/updating.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/updating.md b/docs/updating.md index b23ce32dc..1839edc4c 100644 --- a/docs/updating.md +++ b/docs/updating.md @@ -2,6 +2,10 @@ To update your freqtrade installation, please use one of the below methods, corresponding to your installation method. +!!! Note "Tracking changes" + Breaking changes / changed behavior will be documented in the changelog that is posted alongside every release. + For the develop branch, please follow PR's to avoid being surprised by changes. + ## docker-compose !!! Note "Legacy installations using the `master` image" From baefda80d19e3eaf14e2ec6a600dcb8c456a3ccf Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Apr 2022 18:02:02 +0200 Subject: [PATCH 080/254] Enable flake8 E226 rule --- freqtrade/configuration/PeriodicCache.py | 2 +- freqtrade/configuration/load_config.py | 2 +- freqtrade/exchange/exchange.py | 2 +- freqtrade/leverage/interest.py | 6 +-- freqtrade/misc.py | 2 +- freqtrade/optimize/hyperopt_tools.py | 10 ++--- freqtrade/persistence/models.py | 8 ++-- .../plugins/pairlist/VolatilityFilter.py | 2 +- freqtrade/rpc/telegram.py | 3 +- freqtrade/strategy/strategy_helper.py | 4 +- setup.cfg | 4 +- tests/exchange/test_exchange.py | 37 ++++++++++--------- tests/leverage/test_interest.py | 2 +- tests/optimize/test_optimize_reports.py | 2 +- tests/strategy/test_strategy_helpers.py | 2 +- tests/test_persistence.py | 8 ++-- 16 files changed, 50 insertions(+), 46 deletions(-) diff --git a/freqtrade/configuration/PeriodicCache.py b/freqtrade/configuration/PeriodicCache.py index 64fff668e..1a535440d 100644 --- a/freqtrade/configuration/PeriodicCache.py +++ b/freqtrade/configuration/PeriodicCache.py @@ -16,4 +16,4 @@ class PeriodicCache(TTLCache): return ts - offset # Init with smlight offset - super().__init__(maxsize=maxsize, ttl=ttl-1e-5, timer=local_timer, getsizeof=getsizeof) + super().__init__(maxsize=maxsize, ttl=ttl - 1e-5, timer=local_timer, getsizeof=getsizeof) diff --git a/freqtrade/configuration/load_config.py b/freqtrade/configuration/load_config.py index 4ef531c7a..3fcbd1f2f 100644 --- a/freqtrade/configuration/load_config.py +++ b/freqtrade/configuration/load_config.py @@ -31,7 +31,7 @@ def log_config_error_range(path: str, errmsg: str) -> str: offset = int(offsetlist[0]) text = Path(path).read_text() # Fetch an offset of 80 characters around the error line - subtext = text[offset-min(80, offset):offset+80] + subtext = text[offset - min(80, offset):offset + 80] segments = subtext.split('\n') if len(segments) > 3: # Remove first and last lines, to avoid odd truncations diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 82505759a..af3e4c3eb 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2177,7 +2177,7 @@ class Exchange: lev = tier['lev'] if tier_index < len(pair_tiers) - 1: - next_tier = pair_tiers[tier_index+1] + next_tier = pair_tiers[tier_index + 1] next_floor = next_tier['min'] / next_tier['lev'] if next_floor > stake_amount: # Next tier min too high for stake amount return min((tier['max'] / stake_amount), lev) diff --git a/freqtrade/leverage/interest.py b/freqtrade/leverage/interest.py index ff375b05e..367df5821 100644 --- a/freqtrade/leverage/interest.py +++ b/freqtrade/leverage/interest.py @@ -31,13 +31,13 @@ def interest( """ exchange_name = exchange_name.lower() if exchange_name == "binance": - return borrowed * rate * ceil(hours)/twenty_four + return borrowed * rate * ceil(hours) / twenty_four elif exchange_name == "kraken": # Rounded based on https://kraken-fees-calculator.github.io/ - return borrowed * rate * (one+ceil(hours/four)) + return borrowed * rate * (one + ceil(hours / four)) elif exchange_name == "ftx": # As Explained under #Interest rates section in # https://help.ftx.com/hc/en-us/articles/360053007671-Spot-Margin-Trading-Explainer - return borrowed * rate * ceil(hours)/twenty_four + return borrowed * rate * ceil(hours) / twenty_four else: raise OperationalException(f"Leverage not available on {exchange_name} with freqtrade") diff --git a/freqtrade/misc.py b/freqtrade/misc.py index acc7fc2e4..d5572ea0b 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -126,7 +126,7 @@ def format_ms_time(date: int) -> str: convert MS date to readable format. : epoch-string in ms """ - return datetime.fromtimestamp(date/1000.0).strftime('%Y-%m-%dT%H:%M:%S') + return datetime.fromtimestamp(date / 1000.0).strftime('%Y-%m-%dT%H:%M:%S') def deep_merge_dicts(source, destination, allow_null_overrides: bool = True): diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 8c84f772a..8d3c3a266 100755 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -390,8 +390,8 @@ class HyperoptTools(): lambda x: '{} {}'.format( round_coin_value(x['Total profit'], stake_currency, keep_trailing_zeros=True), f"({x['Profit']:,.2%})".rjust(10, ' ') - ).rjust(25+len(stake_currency)) - if x['Total profit'] != 0.0 else '--'.rjust(25+len(stake_currency)), + ).rjust(25 + len(stake_currency)) + if x['Total profit'] != 0.0 else '--'.rjust(25 + len(stake_currency)), axis=1 ) trials = trials.drop(columns=['Total profit']) @@ -399,11 +399,11 @@ class HyperoptTools(): if print_colorized: for i in range(len(trials)): if trials.loc[i]['is_profit']: - for j in range(len(trials.loc[i])-3): + for j in range(len(trials.loc[i]) - 3): trials.iat[i, j] = "{}{}{}".format(Fore.GREEN, str(trials.loc[i][j]), Fore.RESET) if trials.loc[i]['is_best'] and highlight_best: - for j in range(len(trials.loc[i])-3): + for j in range(len(trials.loc[i]) - 3): trials.iat[i, j] = "{}{}{}".format(Style.BRIGHT, str(trials.loc[i][j]), Style.RESET_ALL) @@ -459,7 +459,7 @@ class HyperoptTools(): 'loss', 'is_initial_point', 'is_best'] perc_multi = 100 - param_metrics = [("params_dict."+param) for param in results[0]['params_dict'].keys()] + param_metrics = [("params_dict." + param) for param in results[0]['params_dict'].keys()] trials = trials[base_metrics + param_metrics] base_columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Median profit', 'Total profit', diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 05de39caf..a9c07f12c 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -360,7 +360,7 @@ class LocalTrade(): if self.has_no_leverage: return 0.0 elif not self.is_short: - return (self.amount * self.open_rate) * ((self.leverage-1)/self.leverage) + return (self.amount * self.open_rate) * ((self.leverage - 1) / self.leverage) else: return self.amount @@ -747,7 +747,7 @@ class LocalTrade(): now = (self.close_date or datetime.now(timezone.utc)).replace(tzinfo=None) sec_per_hour = Decimal(3600) total_seconds = Decimal((now - open_date).total_seconds()) - hours = total_seconds/sec_per_hour or zero + hours = total_seconds / sec_per_hour or zero rate = Decimal(interest_rate or self.interest_rate) borrowed = Decimal(self.borrowed) @@ -861,9 +861,9 @@ class LocalTrade(): return 0.0 else: if self.is_short: - profit_ratio = (1 - (close_trade_value/self.open_trade_value)) * leverage + profit_ratio = (1 - (close_trade_value / self.open_trade_value)) * leverage else: - profit_ratio = ((close_trade_value/self.open_trade_value) - 1) * leverage + profit_ratio = ((close_trade_value / self.open_trade_value) - 1) * leverage return float(f"{profit_ratio:.8f}") diff --git a/freqtrade/plugins/pairlist/VolatilityFilter.py b/freqtrade/plugins/pairlist/VolatilityFilter.py index 7a355c291..6aa857c2c 100644 --- a/freqtrade/plugins/pairlist/VolatilityFilter.py +++ b/freqtrade/plugins/pairlist/VolatilityFilter.py @@ -107,7 +107,7 @@ class VolatilityFilter(IPairList): returns = (np.log(daily_candles.close / daily_candles.close.shift(-1))) returns.fillna(0, inplace=True) - volatility_series = returns.rolling(window=self._days).std()*np.sqrt(self._days) + volatility_series = returns.rolling(window=self._days).std() * np.sqrt(self._days) volatility_avg = volatility_series.mean() if self._min_volatility <= volatility_avg <= self._max_volatility: diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index b152b722c..5699b58aa 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -419,7 +419,8 @@ class Telegram(RPCHandler): if prev_avg_price: minus_on_entry = (cur_entry_average - prev_avg_price) / prev_avg_price - dur_entry = cur_entry_datetime - arrow.get(filled_orders[x-1]["order_filled_date"]) + dur_entry = cur_entry_datetime - arrow.get( + filled_orders[x - 1]["order_filled_date"]) days = dur_entry.days hours, remainder = divmod(dur_entry.seconds, 3600) minutes, seconds = divmod(remainder, 60) diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index f07c14e24..a36cb3dbb 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -93,9 +93,9 @@ def stoploss_from_open( return 1 if is_short is True: - stoploss = -1+((1-open_relative_stop)/(1-current_profit)) + stoploss = -1 + ((1 - open_relative_stop) / (1 - current_profit)) else: - stoploss = 1-((1+open_relative_stop)/(1+current_profit)) + stoploss = 1 - ((1 + open_relative_stop) / (1 + current_profit)) # negative stoploss values indicate the requested stop price is higher/lower # (long/short) than the current price diff --git a/setup.cfg b/setup.cfg index 6aaec9d73..f4a90bda7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,7 +39,9 @@ console_scripts = freqtrade = freqtrade.main:main [flake8] -#ignore = +# Default from https://flake8.pycqa.org/en/latest/user/options.html#cmdoption-flake8-ignore +# minus E226 +ignore = E121,E123,E126,E24,E704,W503,W504 max-line-length = 100 max-complexity = 12 exclude = diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index f0417c4c5..cffeec5b3 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -382,11 +382,11 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: ) # min result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss) - expected_result = 2 * (1+0.05) / (1-abs(stoploss)) + expected_result = 2 * (1 + 0.05) / (1 - abs(stoploss)) assert isclose(result, expected_result) # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss, 3.0) - assert isclose(result, expected_result/3) + assert isclose(result, expected_result / 3) # max result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 10000 @@ -401,11 +401,11 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) - expected_result = 2 * 2 * (1+0.05) / (1-abs(stoploss)) + expected_result = 2 * 2 * (1 + 0.05) / (1 - abs(stoploss)) assert isclose(result, expected_result) # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0) - assert isclose(result, expected_result/5) + assert isclose(result, expected_result / 5) # max result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 20000 @@ -420,11 +420,11 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) - expected_result = max(2, 2 * 2) * (1+0.05) / (1-abs(stoploss)) + expected_result = max(2, 2 * 2) * (1 + 0.05) / (1 - abs(stoploss)) assert isclose(result, expected_result) # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10) - assert isclose(result, expected_result/10) + assert isclose(result, expected_result / 10) # min amount and cost are set (amount is minial) markets["ETH/BTC"]["limits"] = { @@ -436,11 +436,11 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss) - expected_result = max(8, 2 * 2) * (1+0.05) / (1-abs(stoploss)) + expected_result = max(8, 2 * 2) * (1 + 0.05) / (1 - abs(stoploss)) assert isclose(result, expected_result) # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 7.0) - assert isclose(result, expected_result/7.0) + assert isclose(result, expected_result / 7.0) # Max result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 1000 @@ -450,7 +450,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: assert isclose(result, expected_result) # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4, 8.0) - assert isclose(result, expected_result/8.0) + assert isclose(result, expected_result / 8.0) # Max result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 1000 @@ -461,7 +461,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: assert isclose(result, expected_result) # With Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) - assert isclose(result, expected_result/12) + assert isclose(result, expected_result / 12) # Max result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 1000 @@ -489,7 +489,7 @@ def test__get_stake_amount_limit(mocker, default_conf) -> None: ) # With Leverage, Contract size 10 result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) - assert isclose(result, (expected_result/12) * 10.0) + assert isclose(result, (expected_result / 12) * 10.0) # Max result = exchange.get_max_pair_stake_amount('ETH/BTC', 2) assert result == 10000 @@ -510,7 +510,7 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: PropertyMock(return_value=markets) ) result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss) - expected_result = max(0.0001, 0.001 * 0.020405) * (1+0.05) / (1-abs(stoploss)) + expected_result = max(0.0001, 0.001 * 0.020405) * (1 + 0.05) / (1 - abs(stoploss)) assert round(result, 8) == round(expected_result, 8) # Max result = exchange.get_max_pair_stake_amount('ETH/BTC', 2.0) @@ -518,12 +518,12 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: # Leverage result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0) - assert round(result, 8) == round(expected_result/3, 8) + assert round(result, 8) == round(expected_result / 3, 8) # Contract_size markets["ETH/BTC"]["contractSize"] = 0.1 result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0) - assert round(result, 8) == round((expected_result/3), 8) + assert round(result, 8) == round((expected_result / 3), 8) # Max result = exchange.get_max_pair_stake_amount('ETH/BTC', 12.0) @@ -2691,9 +2691,10 @@ async def test__async_get_trade_history_time(default_conf, mocker, caplog, excha # Monkey-patch async function exchange._api_async.fetch_trades = MagicMock(side_effect=mock_get_trade_hist) pair = 'ETH/BTC' - ret = await exchange._async_get_trade_history_time(pair, - since=fetch_trades_result[0]['timestamp'], - until=fetch_trades_result[-1]['timestamp']-1) + ret = await exchange._async_get_trade_history_time( + pair, + since=fetch_trades_result[0]['timestamp'], + until=fetch_trades_result[-1]['timestamp'] - 1) assert type(ret) is tuple assert ret[0] == pair assert type(ret[1]) is list @@ -2729,7 +2730,7 @@ async def test__async_get_trade_history_time_empty(default_conf, mocker, caplog, exchange._async_fetch_trades = MagicMock(side_effect=mock_get_trade_hist) pair = 'ETH/BTC' ret = await exchange._async_get_trade_history_time(pair, since=trades_history[0][0], - until=trades_history[-1][0]-1) + until=trades_history[-1][0] - 1) assert type(ret) is tuple assert ret[0] == pair assert type(ret[1]) is list diff --git a/tests/leverage/test_interest.py b/tests/leverage/test_interest.py index c7e787bdb..6b189ce50 100644 --- a/tests/leverage/test_interest.py +++ b/tests/leverage/test_interest.py @@ -6,7 +6,7 @@ import pytest from freqtrade.leverage import interest -ten_mins = Decimal(1/6) +ten_mins = Decimal(1 / 6) five_hours = Decimal(5.0) twentyfive_hours = Decimal(25.0) diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index ad9bcd978..d40fc1e2f 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -190,7 +190,7 @@ def test_store_backtest_stats(testdatadir, mocker): assert dump_mock.call_count == 3 assert isinstance(dump_mock.call_args_list[0][0][0], Path) - assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir/'backtest-result')) + assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir / 'backtest-result')) dump_mock.reset_mock() filename = testdatadir / 'testresult.json' diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py index 205fb4dac..65fb9f6dc 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -164,7 +164,7 @@ def test_stoploss_from_absolute(): assert pytest.approx(stoploss_from_absolute(90, 100, True)) == 0 assert pytest.approx(stoploss_from_absolute(100, 100, True)) == 0 - assert pytest.approx(stoploss_from_absolute(110, 100, True)) == -(1 - (110/100)) + assert pytest.approx(stoploss_from_absolute(110, 100, True)) == -(1 - (110 / 100)) assert pytest.approx(stoploss_from_absolute(110, 100, True)) == 0.1 assert pytest.approx(stoploss_from_absolute(105, 100, True)) == 0.05 assert pytest.approx(stoploss_from_absolute(100, 0, True)) == 1 diff --git a/tests/test_persistence.py b/tests/test_persistence.py index ecac561f8..801e0e35f 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -119,7 +119,7 @@ def test_set_stop_loss_isolated_liq(fee): assert trade.stop_loss is None assert trade.initial_stop_loss is None - trade._set_stop_loss(0.1, (1.0/9.0)) + trade._set_stop_loss(0.1, (1.0 / 9.0)) assert trade.liquidation_price == 0.09 assert trade.stop_loss == 0.1 assert trade.initial_stop_loss == 0.1 @@ -160,7 +160,7 @@ def test_set_stop_loss_isolated_liq(fee): assert trade.stop_loss is None assert trade.initial_stop_loss is None - trade._set_stop_loss(0.08, (1.0/9.0)) + trade._set_stop_loss(0.08, (1.0 / 9.0)) assert trade.liquidation_price == 0.09 assert trade.stop_loss == 0.08 assert trade.initial_stop_loss == 0.08 @@ -171,13 +171,13 @@ def test_set_stop_loss_isolated_liq(fee): assert trade.initial_stop_loss == 0.08 trade.set_isolated_liq(0.07) - trade._set_stop_loss(0.1, (1.0/8.0)) + trade._set_stop_loss(0.1, (1.0 / 8.0)) assert trade.liquidation_price == 0.07 assert trade.stop_loss == 0.07 assert trade.initial_stop_loss == 0.08 # Stop doesn't move stop higher - trade._set_stop_loss(0.1, (1.0/9.0)) + trade._set_stop_loss(0.1, (1.0 / 9.0)) assert trade.liquidation_price == 0.07 assert trade.stop_loss == 0.07 assert trade.initial_stop_loss == 0.08 From d9039152ba8de5ca13af6ec7b85f5c17bfe9489d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Apr 2022 19:44:47 +0200 Subject: [PATCH 081/254] Add "get backtest historic results" endpoint --- freqtrade/data/btanalysis.py | 26 +++++++++++++++++++++++- freqtrade/rpc/api_server/api_backtest.py | 11 +++++++++- freqtrade/rpc/api_server/api_schemas.py | 7 +++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index db7ef66fc..4c178d8c8 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -160,6 +160,30 @@ def _load_and_merge_backtest_result(strategy_name: str, filename: Path, results: break +def _get_backtest_files(dirname: Path) -> List[Path]: + return reversed(sorted(dirname.glob('backtest-result-*-[0-9][0-9].json'))) + + +def get_backtest_resultlist(dirname: Path): + """ + Get list of backtest results read from metadata files + """ + results = [] + for filename in _get_backtest_files(dirname): + metadata = load_backtest_metadata(filename) + if not metadata: + continue + for s, v in metadata.items(): + results.append({ + 'filename': filename.name, + 'strategy': s, + 'run_id': v['run_id'], + 'backtest_start_time': v['backtest_start_time'], + + }) + return results + + def find_existing_backtest_stats(dirname: Union[Path, str], run_ids: Dict[str, str], min_backtest_date: datetime = None) -> Dict[str, Any]: """ @@ -179,7 +203,7 @@ def find_existing_backtest_stats(dirname: Union[Path, str], run_ids: Dict[str, s } # Weird glob expression here avoids including .meta.json files. - for filename in reversed(sorted(dirname.glob('backtest-result-*-[0-9][0-9].json'))): + for filename in _get_backtest_files(dirname): metadata = load_backtest_metadata(filename) if not metadata: # Files are sorted from newest to oldest. When file without metadata is encountered it diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 757ed8aac..a44a98494 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -1,13 +1,16 @@ import asyncio import logging from copy import deepcopy +from typing import List from fastapi import APIRouter, BackgroundTasks, Depends from freqtrade.configuration.config_validation import validate_config_consistency +from freqtrade.data.btanalysis import get_backtest_resultlist from freqtrade.enums import BacktestState from freqtrade.exceptions import DependencyException -from freqtrade.rpc.api_server.api_schemas import BacktestRequest, BacktestResponse +from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestRequest, + BacktestResponse) from freqtrade.rpc.api_server.deps import get_config, is_webserver_mode from freqtrade.rpc.api_server.webserver import ApiServer from freqtrade.rpc.rpc import RPCException @@ -200,3 +203,9 @@ def api_backtest_abort(ws_mode=Depends(is_webserver_mode)): "progress": 0, "status_msg": "Backtest ended", } + + +@router.get('/backtest/history', response_model=List[BacktestHistoryEntry], tags=['webserver', 'backtest']) +def api_backtest_history(config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): + # Get backtest result history, read from metadata files + return get_backtest_resultlist(config['user_data_dir'] / 'backtest_results') diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index ae797edad..a9135cce2 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -421,6 +421,13 @@ class BacktestResponse(BaseModel): backtest_result: Optional[Dict[str, Any]] +class BacktestHistoryEntry(BaseModel): + filename: str + strategy: str + run_id: str + backtest_start_time: int + + class SysInfo(BaseModel): cpu_pct: List[float] ram_pct: float From 85e7deb2cd60c235464b92dfdf4fd8ac630dcb41 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Apr 2022 20:04:47 +0200 Subject: [PATCH 082/254] Add loading of historic backtest result --- freqtrade/rpc/api_server/api_backtest.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index a44a98494..9eeafd4d1 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -6,7 +6,7 @@ from typing import List from fastapi import APIRouter, BackgroundTasks, Depends from freqtrade.configuration.config_validation import validate_config_consistency -from freqtrade.data.btanalysis import get_backtest_resultlist +from freqtrade.data.btanalysis import get_backtest_resultlist, load_backtest_stats from freqtrade.enums import BacktestState from freqtrade.exceptions import DependencyException from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestRequest, @@ -209,3 +209,17 @@ def api_backtest_abort(ws_mode=Depends(is_webserver_mode)): def api_backtest_history(config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): # Get backtest result history, read from metadata files return get_backtest_resultlist(config['user_data_dir'] / 'backtest_results') + + +@router.get('/backtest/history/result', response_model=BacktestResponse, tags=['webserver', 'backtest']) +def api_backtest_history_result(filename: str, config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): + # Get backtest result history, read from metadata files + fn = config['user_data_dir'] / 'backtest_results' / filename + return { + "status": "ended", + "running": False, + "step": "", + "progress": 1, + "status_msg": "Historic result", + "backtest_result": load_backtest_stats(fn) + } From 4254d8665861df31fe757f5b957d1194dd7562fb Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 11 Apr 2022 20:32:02 +0200 Subject: [PATCH 083/254] Move test-backtestfiles to separate directory --- tests/commands/test_commands.py | 2 +- tests/data/test_btanalysis.py | 27 ++++++++++--------- tests/optimize/test_optimize_reports.py | 10 +++---- tests/test_plotting.py | 8 +++--- .../{ => backtest_results}/.last_result.json | 0 .../backtest-result_multistrat.json | 0 .../backtest-result_new.json | 0 .../backtest-result_new.meta.json | 6 +++++ 8 files changed, 30 insertions(+), 23 deletions(-) rename tests/testdata/{ => backtest_results}/.last_result.json (100%) rename tests/testdata/{ => backtest_results}/backtest-result_multistrat.json (100%) rename tests/testdata/{ => backtest_results}/backtest-result_new.json (100%) create mode 100644 tests/testdata/backtest_results/backtest-result_new.meta.json diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 22869638b..1431bd22a 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -1429,7 +1429,7 @@ def test_backtesting_show(mocker, testdatadir, capsys): args = [ "backtesting-show", "--export-filename", - f"{testdatadir / 'backtest-result_new.json'}", + f"{testdatadir / 'backtest_results/backtest-result_new.json'}", "--show-pair-list" ] pargs = get_args(args) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index f4275edd9..2b53e4900 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -27,18 +27,19 @@ def test_get_latest_backtest_filename(testdatadir, mocker): with pytest.raises(ValueError, match=r"Directory .* does not seem to contain .*"): - get_latest_backtest_filename(testdatadir.parent) + get_latest_backtest_filename(testdatadir) - res = get_latest_backtest_filename(testdatadir) + testdir_bt = testdatadir / "backtest_results" + res = get_latest_backtest_filename(testdir_bt) assert res == 'backtest-result_new.json' - res = get_latest_backtest_filename(str(testdatadir)) + res = get_latest_backtest_filename(str(testdir_bt)) assert res == 'backtest-result_new.json' mocker.patch("freqtrade.data.btanalysis.json_load", return_value={}) with pytest.raises(ValueError, match=r"Invalid '.last_result.json' format."): - get_latest_backtest_filename(testdatadir) + get_latest_backtest_filename(testdir_bt) def test_get_latest_hyperopt_file(testdatadir): @@ -81,7 +82,7 @@ def test_load_backtest_data_old_format(testdatadir, mocker): def test_load_backtest_data_new_format(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) assert isinstance(bt_data, DataFrame) assert set(bt_data.columns) == set(BT_DATA_COLUMNS + ['close_timestamp', 'open_timestamp']) @@ -92,19 +93,19 @@ def test_load_backtest_data_new_format(testdatadir): assert bt_data.equals(bt_data2) # Test loading from folder (must yield same result) - bt_data3 = load_backtest_data(testdatadir) + bt_data3 = load_backtest_data(testdatadir / "backtest_results") assert bt_data.equals(bt_data3) with pytest.raises(ValueError, match=r"File .* does not exist\."): load_backtest_data(str("filename") + "nofile") with pytest.raises(ValueError, match=r"Unknown dataformat."): - load_backtest_data(testdatadir / LAST_BT_RESULT_FN) + load_backtest_data(testdatadir / "backtest_results" / LAST_BT_RESULT_FN) def test_load_backtest_data_multi(testdatadir): - filename = testdatadir / "backtest-result_multistrat.json" + filename = testdatadir / "backtest_results/backtest-result_multistrat.json" for strategy in ('StrategyTestV2', 'TestStrategy'): bt_data = load_backtest_data(filename, strategy=strategy) assert isinstance(bt_data, DataFrame) @@ -182,7 +183,7 @@ def test_extract_trades_of_period(testdatadir): def test_analyze_trade_parallelism(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) res = analyze_trade_parallelism(bt_data, "5m") @@ -256,7 +257,7 @@ def test_combine_dataframes_with_mean_no_data(testdatadir): def test_create_cum_profit(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") @@ -272,7 +273,7 @@ def test_create_cum_profit(testdatadir): def test_create_cum_profit1(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) # Move close-time to "off" the candle, to make sure the logic still works bt_data.loc[:, 'close_date'] = bt_data.loc[:, 'close_date'] + DateOffset(seconds=20) @@ -294,7 +295,7 @@ def test_create_cum_profit1(testdatadir): def test_calculate_max_drawdown(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) _, hdate, lowdate, hval, lval, drawdown = calculate_max_drawdown( bt_data, value_col="profit_abs") @@ -318,7 +319,7 @@ def test_calculate_max_drawdown(testdatadir): def test_calculate_csum(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) csum_min, csum_max = calculate_csum(bt_data) diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index d40fc1e2f..05c0bf575 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -228,7 +228,7 @@ def test_generate_pair_metrics(): def test_generate_daily_stats(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) res = generate_daily_stats(bt_data) assert isinstance(res, dict) @@ -248,7 +248,7 @@ def test_generate_daily_stats(testdatadir): def test_generate_trading_stats(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) res = generate_trading_stats(bt_data) assert isinstance(res, dict) @@ -332,7 +332,7 @@ def test_generate_sell_reason_stats(): def test_text_table_strategy(testdatadir): - filename = testdatadir / "backtest-result_multistrat.json" + filename = testdatadir / "backtest_results/backtest-result_multistrat.json" bt_res_data = load_backtest_stats(filename) bt_res_data_comparison = bt_res_data.pop('strategy_comparison') @@ -364,7 +364,7 @@ def test_generate_edge_table(): def test_generate_periodic_breakdown_stats(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename).to_dict(orient='records') res = generate_periodic_breakdown_stats(bt_data, 'day') @@ -392,7 +392,7 @@ def test__get_resample_from_period(): def test_show_sorted_pairlist(testdatadir, default_conf, capsys): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_stats(filename) default_conf['backtest_show_pair_list'] = True diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 940639465..97f367608 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -157,7 +157,7 @@ def test_plot_trades(testdatadir, caplog): assert fig == fig1 assert log_has("No trades found.", caplog) pair = "ADA/BTC" - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" trades = load_backtest_data(filename) trades = trades.loc[trades['pair'] == pair] @@ -298,7 +298,7 @@ def test_generate_plot_file(mocker, caplog): def test_add_profit(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" bt_data = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") @@ -318,7 +318,7 @@ def test_add_profit(testdatadir): def test_generate_profit_graph(testdatadir): - filename = testdatadir / "backtest-result_new.json" + filename = testdatadir / "backtest_results/backtest-result_new.json" trades = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") pairs = ["TRX/BTC", "XLM/BTC"] @@ -456,7 +456,7 @@ def test_plot_profit(default_conf, mocker, testdatadir): match=r"No trades found, cannot generate Profit-plot.*"): plot_profit(default_conf) - default_conf['exportfilename'] = testdatadir / "backtest-result_new.json" + default_conf['exportfilename'] = testdatadir / "backtest_results/backtest-result_new.json" plot_profit(default_conf) diff --git a/tests/testdata/.last_result.json b/tests/testdata/backtest_results/.last_result.json similarity index 100% rename from tests/testdata/.last_result.json rename to tests/testdata/backtest_results/.last_result.json diff --git a/tests/testdata/backtest-result_multistrat.json b/tests/testdata/backtest_results/backtest-result_multistrat.json similarity index 100% rename from tests/testdata/backtest-result_multistrat.json rename to tests/testdata/backtest_results/backtest-result_multistrat.json diff --git a/tests/testdata/backtest-result_new.json b/tests/testdata/backtest_results/backtest-result_new.json similarity index 100% rename from tests/testdata/backtest-result_new.json rename to tests/testdata/backtest_results/backtest-result_new.json diff --git a/tests/testdata/backtest_results/backtest-result_new.meta.json b/tests/testdata/backtest_results/backtest-result_new.meta.json new file mode 100644 index 000000000..57ecdb19d --- /dev/null +++ b/tests/testdata/backtest_results/backtest-result_new.meta.json @@ -0,0 +1,6 @@ +{ + "StrategyTestV3": { + "run_id": "430d0271075ef327edbb23088f4db4ebe51a3dbf", + "backtest_start_time": 1648904006 + } +} From c8e468783393d44c2bb5395e2994c0950720ddc0 Mon Sep 17 00:00:00 2001 From: Nicolas Papp Date: Mon, 11 Apr 2022 16:41:48 -0300 Subject: [PATCH 084/254] Plots and hyperopt --- freqtrade/data/btanalysis.py | 3 ++ freqtrade/plot/plotting.py | 44 ++++++++++++++++++++--------- tests/optimize/test_hyperoptloss.py | 1 + 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 3c54b0eeb..6858deb69 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -445,6 +445,9 @@ def _calc_drawdown_series(profit_results: pd.DataFrame, *, date_col: str, value_ cumulative_balance = starting_balance + max_drawdown_df['cumulative'] max_balance = starting_balance + max_drawdown_df['high_value'] max_drawdown_df['drawdown_relative'] = ((max_balance - cumulative_balance) / max_balance) + else: + # This is not completely accurate, + max_drawdown_df['drawdown_relative'] = ((max_drawdown_df['high_value'] - max_drawdown_df['cumulative']) / max_drawdown_df['high_value']) return max_drawdown_df diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 747248be7..3d651c1d9 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -1,6 +1,7 @@ import logging from pathlib import Path from typing import Any, Dict, List, Optional +from numpy import number import pandas as pd @@ -158,12 +159,12 @@ def add_profit(fig, row, data: pd.DataFrame, column: str, name: str) -> make_sub def add_max_drawdown(fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame, - timeframe: str) -> make_subplots: + timeframe: str, starting_balance: number) -> make_subplots: """ Add scatter points indicating max drawdown """ try: - _, highdate, lowdate, _, _, max_drawdown = calculate_max_drawdown(trades) + _, highdate, lowdate, _, _, max_drawdown = calculate_max_drawdown(trades, starting_balance=starting_balance) drawdown = go.Scatter( x=[highdate, lowdate], @@ -188,22 +189,33 @@ def add_max_drawdown(fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame, return fig -def add_underwater(fig, row, trades: pd.DataFrame) -> make_subplots: +def add_underwater(fig, row, trades: pd.DataFrame, starting_balance: number) -> make_subplots: """ - Add underwater plot + Add underwater plots """ try: - underwater = calculate_underwater(trades, value_col="profit_abs") + underwater = calculate_underwater(trades, value_col="profit_abs", starting_balance=starting_balance) - underwater = go.Scatter( + underwater_plot = go.Scatter( x=underwater['date'], y=underwater['drawdown'], name="Underwater Plot", fill='tozeroy', fillcolor='#cc362b', - line={'color': '#cc362b'}, + line={'color': '#cc362b'} ) - fig.add_trace(underwater, row, 1) + + underwater_plot_relative = go.Scatter( + x=underwater['date'], + y=(-underwater['drawdown_relative']), + name="Underwater Plot (%)", + fill='tozeroy', + fillcolor='green', + line={'color': 'green'} + ) + + fig.add_trace(underwater_plot, row, 1) + fig.add_trace(underwater_plot_relative, row+1, 1) except ValueError: logger.warning("No trades found - not plotting underwater plot") return fig @@ -506,7 +518,8 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame], - trades: pd.DataFrame, timeframe: str, stake_currency: str) -> go.Figure: + trades: pd.DataFrame, timeframe: str, stake_currency: str, + starting_balance: number) -> go.Figure: # Combine close-values for all pairs, rename columns to "pair" try: df_comb = combine_dataframes_with_mean(data, "close") @@ -530,8 +543,8 @@ def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame], name='Avg close price', ) - fig = make_subplots(rows=5, cols=1, shared_xaxes=True, - row_heights=[1, 1, 1, 0.5, 1], + fig = make_subplots(rows=6, cols=1, shared_xaxes=True, + row_heights=[1, 1, 1, 0.5, 0.75, 0.75], vertical_spacing=0.05, subplot_titles=[ "AVG Close Price", @@ -539,6 +552,7 @@ def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame], "Profit per pair", "Parallelism", "Underwater", + "Relative Drawdown", ]) fig['layout'].update(title="Freqtrade Profit plot") fig['layout']['yaxis1'].update(title='Price') @@ -546,14 +560,16 @@ def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame], fig['layout']['yaxis3'].update(title=f'Profit {stake_currency}') fig['layout']['yaxis4'].update(title='Trade count') fig['layout']['yaxis5'].update(title='Underwater Plot') + fig['layout']['yaxis6'].update(title='Underwater Plot Relative (%)', tickformat=',.2%') fig['layout']['xaxis']['rangeslider'].update(visible=False) fig.update_layout(modebar_add=["v1hovermode", "toggleSpikeLines"]) fig.add_trace(avgclose, 1, 1) fig = add_profit(fig, 2, df_comb, 'cum_profit', 'Profit') - fig = add_max_drawdown(fig, 2, trades, df_comb, timeframe) + fig = add_max_drawdown(fig, 2, trades, df_comb, timeframe, starting_balance) fig = add_parallelism(fig, 4, trades, timeframe) - fig = add_underwater(fig, 5, trades) + # Two rows consumed + fig = add_underwater(fig, 5, trades, starting_balance) for pair in pairs: profit_col = f'cum_profit_{pair}' @@ -668,7 +684,7 @@ def plot_profit(config: Dict[str, Any]) -> None: # this could be useful to gauge the overall market trend fig = generate_profit_graph(plot_elements['pairs'], plot_elements['ohlcv'], trades, config['timeframe'], - config.get('stake_currency', '')) + config.get('stake_currency', ''), config['available_capital']) store_plot_file(fig, filename='freqtrade-profit-plot.html', directory=config['user_data_dir'] / 'plot', auto_open=config.get('plot_auto_open', False)) diff --git a/tests/optimize/test_hyperoptloss.py b/tests/optimize/test_hyperoptloss.py index e3f6daf6c..aac02305e 100644 --- a/tests/optimize/test_hyperoptloss.py +++ b/tests/optimize/test_hyperoptloss.py @@ -85,6 +85,7 @@ def test_loss_calculation_has_limited_profit(hyperopt_conf, hyperopt_results) -> "SharpeHyperOptLoss", "SharpeHyperOptLossDaily", "MaxDrawDownHyperOptLoss", + "MaxDrawDownRelativeHyperOptLoss", "CalmarHyperOptLoss", "ProfitDrawDownHyperOptLoss", From 0c87702545c587525054b90bd6ca2c0f8298e7af Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Apr 2022 06:28:37 +0200 Subject: [PATCH 085/254] test for backtest history --- tests/rpc/test_rpc_apiserver.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 54bf07dc2..ee54e95dd 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1581,6 +1581,34 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): assert result['status_msg'] == 'Backtest reset' +def test_api_backtest_history(botclient, mocker, testdatadir): + ftbot, client = botclient + mocker.patch('freqtrade.data.btanalysis._get_backtest_files', + return_value=[ + testdatadir / 'backtest_results/backtest-result_multistrat.json', + testdatadir / 'backtest_results/backtest-result_new.json' + ]) + + rc = client_get(client, f"{BASE_URI}/backtest/history") + assert_response(rc, 502) + ftbot.config['user_data_dir'] = testdatadir + ftbot.config['runmode'] = RunMode.WEBSERVER + + rc = client_get(client, f"{BASE_URI}/backtest/history") + assert_response(rc) + result = rc.json() + assert len(result) == 1 + fn = result[0]['filename'] + rc = client_get(client, f"{BASE_URI}/backtest/history/result?filename={fn}") + assert_response(rc) + result2 = rc.json() + assert result2 + assert result2['status'] == 'ended' + assert not result2['running'] + assert result2['progress'] == 1 + assert result2['backtest_result']['strategy'][CURRENT_TEST_STRATEGY] + + def test_health(botclient): ftbot, client = botclient From dd5693f4e5ed238513fd8b45916eaa1f6a952ee2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 12 Apr 2022 07:21:20 +0200 Subject: [PATCH 086/254] Add note about binance Futures trading rules closes #6683 --- docs/exchanges.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index b808096d2..18a7af5a1 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -64,7 +64,10 @@ Binance supports [time_in_force](configuration.md#understand-order_time_in_force For Binance, please add `"BNB/"` to your blacklist to avoid issues. Accounts having BNB accounts use this to pay for fees - if your first trade happens to be on `BNB`, further trades will consume this position and make the initial BNB trade unsellable as the expected amount is not there anymore. -### Binance Futures' order pricing +### Binance Futures + +Binance has specific (unfortunately complex) [Futures Trading Quantitative Rules](https://www.binance.com/en/support/faq/4f462ebe6ff445d4a170be7d9e897272) which need to be followed, and which prohibit a too low stake-amount (among others) for too many orders. +Violating these rules will result in a trading restriction. When trading on Binance Futures market, orderbook must be used because there is no price ticker data for futures. 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 087/254] 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 4ac54a76af7f61815a7635768c66e1c42f70930d Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 13 Apr 2022 06:47:39 +0200 Subject: [PATCH 088/254] Add strategy as mandatory argument --- freqtrade/data/btanalysis.py | 11 +++++++++-- freqtrade/rpc/api_server/api_backtest.py | 13 ++++++++++--- tests/rpc/test_rpc_apiserver.py | 3 ++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 4c178d8c8..ffa7fe0c0 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -149,7 +149,14 @@ def load_backtest_stats(filename: Union[Path, str]) -> Dict[str, Any]: return data -def _load_and_merge_backtest_result(strategy_name: str, filename: Path, results: Dict[str, Any]): +def load_and_merge_backtest_result(strategy_name: str, filename: Path, results: Dict[str, Any]): + """ + Load one strategy from multi-strategy result + and merge it with results + :param strategy_name: Name of the strategy contained in the result + :param filename: Backtest-result-filename to load + :param results: dict to merge the result to. + """ bt_data = load_backtest_stats(filename) for k in ('metadata', 'strategy'): results[k][strategy_name] = bt_data[k][strategy_name] @@ -226,7 +233,7 @@ def find_existing_backtest_stats(dirname: Union[Path, str], run_ids: Dict[str, s if strategy_metadata['run_id'] == run_id: del run_ids[strategy_name] - _load_and_merge_backtest_result(strategy_name, filename, results) + load_and_merge_backtest_result(strategy_name, filename, results) if len(run_ids) == 0: break diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 9eeafd4d1..b11b28685 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -6,7 +6,7 @@ from typing import List from fastapi import APIRouter, BackgroundTasks, Depends from freqtrade.configuration.config_validation import validate_config_consistency -from freqtrade.data.btanalysis import get_backtest_resultlist, load_backtest_stats +from freqtrade.data.btanalysis import get_backtest_resultlist, load_and_merge_backtest_result from freqtrade.enums import BacktestState from freqtrade.exceptions import DependencyException from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestRequest, @@ -212,14 +212,21 @@ def api_backtest_history(config=Depends(get_config), ws_mode=Depends(is_webserve @router.get('/backtest/history/result', response_model=BacktestResponse, tags=['webserver', 'backtest']) -def api_backtest_history_result(filename: str, config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): +def api_backtest_history_result(filename: str, strategy: str, config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): # Get backtest result history, read from metadata files fn = config['user_data_dir'] / 'backtest_results' / filename + results = { + 'metadata': {}, + 'strategy': {}, + 'strategy_comparison': [], + } + + load_and_merge_backtest_result(strategy, fn, results) return { "status": "ended", "running": False, "step": "", "progress": 1, "status_msg": "Historic result", - "backtest_result": load_backtest_stats(fn) + "backtest_result": results, } diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index ee54e95dd..52ab35c36 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1599,7 +1599,8 @@ def test_api_backtest_history(botclient, mocker, testdatadir): result = rc.json() assert len(result) == 1 fn = result[0]['filename'] - rc = client_get(client, f"{BASE_URI}/backtest/history/result?filename={fn}") + strategy = result[0]['strategy'] + rc = client_get(client, f"{BASE_URI}/backtest/history/result?filename={fn}&strategy={strategy}") assert_response(rc) result2 = rc.json() assert result2 From f89b64c972a5bb7b45b5fc99a0b0b33a510445ac Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 13 Apr 2022 06:55:47 +0200 Subject: [PATCH 089/254] Improve test by having multistrat.meta file available --- freqtrade/data/btanalysis.py | 2 +- freqtrade/rpc/api_server/api_backtest.py | 4 ++-- freqtrade/rpc/api_server/api_v1.py | 3 ++- tests/rpc/test_rpc_apiserver.py | 7 +++++-- .../backtest-result_multistrat.meta.json | 10 ++++++++++ 5 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 tests/testdata/backtest_results/backtest-result_multistrat.meta.json diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index ffa7fe0c0..8abcc6747 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -168,7 +168,7 @@ def load_and_merge_backtest_result(strategy_name: str, filename: Path, results: def _get_backtest_files(dirname: Path) -> List[Path]: - return reversed(sorted(dirname.glob('backtest-result-*-[0-9][0-9].json'))) + return list(reversed(sorted(dirname.glob('backtest-result-*-[0-9][0-9].json')))) def get_backtest_resultlist(dirname: Path): diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index b11b28685..a902ea984 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -1,7 +1,7 @@ import asyncio import logging from copy import deepcopy -from typing import List +from typing import Any, Dict, List from fastapi import APIRouter, BackgroundTasks, Depends @@ -215,7 +215,7 @@ def api_backtest_history(config=Depends(get_config), ws_mode=Depends(is_webserve def api_backtest_history_result(filename: str, strategy: str, config=Depends(get_config), ws_mode=Depends(is_webserver_mode)): # Get backtest result history, read from metadata files fn = config['user_data_dir'] / 'backtest_results' / filename - results = { + results: Dict[str, Any] = { 'metadata': {}, 'strategy': {}, 'strategy_comparison': [], diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index d96154824..5021c99f9 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -35,7 +35,8 @@ logger = logging.getLogger(__name__) # 1.13: forcebuy supports stake_amount # versions 2.xx -> futures/short branch # 2.14: Add entry/exit orders to trade response -API_VERSION = 2.14 +# 2.15: Add backtest history endpoints +API_VERSION = 2.15 # Public API, requires no auth. router_public = APIRouter() diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 52ab35c36..af8361571 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1597,8 +1597,9 @@ def test_api_backtest_history(botclient, mocker, testdatadir): rc = client_get(client, f"{BASE_URI}/backtest/history") assert_response(rc) result = rc.json() - assert len(result) == 1 + assert len(result) == 3 fn = result[0]['filename'] + assert fn == "backtest-result_multistrat.json" strategy = result[0]['strategy'] rc = client_get(client, f"{BASE_URI}/backtest/history/result?filename={fn}&strategy={strategy}") assert_response(rc) @@ -1607,7 +1608,9 @@ def test_api_backtest_history(botclient, mocker, testdatadir): assert result2['status'] == 'ended' assert not result2['running'] assert result2['progress'] == 1 - assert result2['backtest_result']['strategy'][CURRENT_TEST_STRATEGY] + # Only one strategy loaded - even though we use multiresult + assert len(result2['backtest_result']['strategy']) == 1 + assert result2['backtest_result']['strategy'][strategy] def test_health(botclient): diff --git a/tests/testdata/backtest_results/backtest-result_multistrat.meta.json b/tests/testdata/backtest_results/backtest-result_multistrat.meta.json new file mode 100644 index 000000000..906edcece --- /dev/null +++ b/tests/testdata/backtest_results/backtest-result_multistrat.meta.json @@ -0,0 +1,10 @@ +{ + "StrategyTestV2": { + "run_id": "430d0271075ef327edbb23088f4db4ebe51a3dbf", + "backtest_start_time": 1648904006 + }, + "TestStrategy": { + "run_id": "110d0271075ef327edbb23085102b4ebe51a3d55", + "backtest_start_time": 1648904006 + } +} 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 090/254] 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 b1a6db8d29dadf33ea9349ffa324c87d91ebc5b1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 13 Apr 2022 19:24:21 +0200 Subject: [PATCH 091/254] Fix bad usage of base_currency --- freqtrade/rpc/telegram.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 5699b58aa..c2531fec3 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -387,7 +387,7 @@ class Telegram(RPCHandler): else: return "\N{CROSS MARK}" - def _prepare_entry_details(self, filled_orders: List, base_currency: str, is_open: bool): + def _prepare_entry_details(self, filled_orders: List, quote_currency: str, is_open: bool): """ Prepare details of trade with entry adjustment enabled """ @@ -405,7 +405,7 @@ class Telegram(RPCHandler): if x == 0: lines.append(f"*Entry #{x+1}:*") lines.append( - f"*Entry Amount:* {cur_entry_amount} ({order['cost']:.8f} {base_currency})") + f"*Entry Amount:* {cur_entry_amount} ({order['cost']:.8f} {quote_currency})") lines.append(f"*Average Entry Price:* {cur_entry_average}") else: sumA = 0 @@ -429,7 +429,7 @@ class Telegram(RPCHandler): lines.append("({})".format(cur_entry_datetime .humanize(granularity=["day", "hour", "minute"]))) lines.append( - f"*Entry Amount:* {cur_entry_amount} ({order['cost']:.8f} {base_currency})") + f"*Entry Amount:* {cur_entry_amount} ({order['cost']:.8f} {quote_currency})") lines.append(f"*Average Entry Price:* {cur_entry_average} " f"({price_to_1st_entry:.2%} from 1st entry rate)") lines.append(f"*Order filled at:* {order['order_filled_date']}") @@ -472,7 +472,7 @@ class Telegram(RPCHandler): "*Current Pair:* {pair}", "*Direction:* " + ("`Short`" if r.get('is_short') else "`Long`"), "*Leverage:* `{leverage}`" if r.get('leverage') else "", - "*Amount:* `{amount} ({stake_amount} {base_currency})`", + "*Amount:* `{amount} ({stake_amount} {quote_currency})`", "*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "", "*Exit Reason:* `{exit_reason}`" if r['exit_reason'] else "", ] From 4acb77305a78940b64f1ee5550130da503dc0ced Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 13 Apr 2022 19:33:27 +0200 Subject: [PATCH 092/254] 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 093/254] 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 094/254] 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', From d23d8303465252aff20d46a0ce85b710669ade50 Mon Sep 17 00:00:00 2001 From: kokimame Date: Fri, 15 Apr 2022 00:35:11 +0900 Subject: [PATCH 095/254] Avoid ignoring the case precision amount = 0 --- freqtrade/exchange/exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index af3e4c3eb..e0da0878d 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -651,7 +651,7 @@ class Exchange: Re-implementation of ccxt internal methods - ensuring we can test the result is correct based on our definitions. """ - if self.markets[pair]['precision']['amount']: + if self.markets[pair]['precision']['amount'] is not None: amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE, precision=self.markets[pair]['precision']['amount'], counting_mode=self.precisionMode, From f61d4d36c337f618086c5e4084d1e9cde5ed9718 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 15 Apr 2022 14:48:09 +0200 Subject: [PATCH 096/254] Add test for 0 precision amount --- tests/exchange/test_exchange.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index cffeec5b3..e99121489 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -231,6 +231,10 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog): (2.34559, 2, 3, 1, 2.345, 'spot'), (2.9999, 2, 3, 1, 2.999, 'spot'), (2.9909, 2, 3, 1, 2.990, 'spot'), + (2.9909, 2, 0, 1, 2, 'spot'), + (29991.5555, 2, 0, 1, 29991, 'spot'), + (29991.5555, 2, -1, 1, 29990, 'spot'), + (29991.5555, 2, -2, 1, 29900, 'spot'), # Tests for Tick-size (2.34559, 4, 0.0001, 1, 2.3455, 'spot'), (2.34559, 4, 0.00001, 1, 2.34559, 'spot'), From 4019c05feeac51680b2c2c8e315b0851bccd63c3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 16 Apr 2022 06:47:56 +0200 Subject: [PATCH 097/254] Update entry/exit timeout documentation the type of order is now an Order, no longer a dictionary. closes #6691 --- docs/strategy-callbacks.md | 16 ++++++------ docs/strategy_migration.md | 4 +-- freqtrade/strategy/interface.py | 26 +++++++++---------- .../subtemplates/strategy_methods_advanced.j2 | 18 ++++++++----- 4 files changed, 34 insertions(+), 30 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index bd32f41c3..7ec600a58 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -418,7 +418,7 @@ The function must return either `True` (cancel order) or `False` (keep order ali ``` python from datetime import datetime, timedelta -from freqtrade.persistence import Trade +from freqtrade.persistence import Trade, Order class AwesomeStrategy(IStrategy): @@ -430,7 +430,7 @@ class AwesomeStrategy(IStrategy): 'exit': 60 * 25 } - def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, + def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', current_time: datetime, **kwargs) -> bool: if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): return True @@ -441,7 +441,7 @@ class AwesomeStrategy(IStrategy): return False - def check_exit_timeout(self, pair: str, trade: Trade, order: dict, + def check_exit_timeout(self, pair: str, trade: Trade, order: 'Order', current_time: datetime, **kwargs) -> bool: if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): return True @@ -459,7 +459,7 @@ class AwesomeStrategy(IStrategy): ``` python from datetime import datetime -from freqtrade.persistence import Trade +from freqtrade.persistence import Trade, Order class AwesomeStrategy(IStrategy): @@ -471,22 +471,22 @@ class AwesomeStrategy(IStrategy): 'exit': 60 * 25 } - def check_entry_timeout(self, pair: str, trade: Trade, order: dict, + def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', current_time: datetime, **kwargs) -> bool: ob = self.dp.orderbook(pair, 1) current_price = ob['bids'][0][0] # Cancel buy order if price is more than 2% above the order. - if current_price > order['price'] * 1.02: + if current_price > order.price * 1.02: return True return False - def check_exit_timeout(self, pair: str, trade: Trade, order: dict, + def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order', current_time: datetime, **kwargs) -> bool: ob = self.dp.orderbook(pair, 1) current_price = ob['asks'][0][0] # Cancel sell order if price is more than 2% below the order. - if current_price < order['price'] * 0.98: + if current_price < order.price * 0.98: return True return False ``` diff --git a/docs/strategy_migration.md b/docs/strategy_migration.md index ac8d3e489..458e80d0e 100644 --- a/docs/strategy_migration.md +++ b/docs/strategy_migration.md @@ -183,11 +183,11 @@ class AwesomeStrategy(IStrategy): ``` python hl_lines="2 6" class AwesomeStrategy(IStrategy): - def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, + def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', current_time: datetime, **kwargs) -> bool: return False - def check_exit_timeout(self, pair: str, trade: 'Trade', order: dict, + def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order', current_time: datetime, **kwargs) -> bool: return False ``` diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index ba2eb9636..afcc1aa99 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -206,18 +206,18 @@ class IStrategy(ABC, HyperStrategyMixin): """ pass - def check_buy_timeout(self, pair: str, trade: Trade, order: dict, + def check_buy_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ DEPRECATED: Please use `check_entry_timeout` instead. """ return False - def check_entry_timeout(self, pair: str, trade: Trade, order: dict, + def check_entry_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ Check entry timeout function callback. - This method can be used to override the enter-timeout. + This method can be used to override the entry-timeout. It is called whenever a limit entry order has been created, and is not yet fully filled. Configuration options in `unfilledtimeout` will be verified before this, @@ -225,8 +225,8 @@ class IStrategy(ABC, HyperStrategyMixin): When not implemented by a strategy, this simply returns False. :param pair: Pair the trade is for - :param trade: trade object. - :param order: Order dictionary as returned from CCXT. + :param trade: Trade object. + :param order: Order object. :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the entry order is cancelled. @@ -234,30 +234,30 @@ class IStrategy(ABC, HyperStrategyMixin): return self.check_buy_timeout( pair=pair, trade=trade, order=order, current_time=current_time) - def check_sell_timeout(self, pair: str, trade: Trade, order: dict, + def check_sell_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ DEPRECATED: Please use `check_exit_timeout` instead. """ return False - def check_exit_timeout(self, pair: str, trade: Trade, order: dict, + def check_exit_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ - Check sell timeout function callback. + Check exit timeout function callback. This method can be used to override the exit-timeout. - It is called whenever a (long) limit sell order or (short) limit buy - has been created, and is not yet fully filled. + It is called whenever a limit exit order has been created, + and is not yet fully filled. Configuration options in `unfilledtimeout` will be verified before this, so ensure to set these timeouts high enough. When not implemented by a strategy, this simply returns False. :param pair: Pair the trade is for - :param trade: trade object. - :param order: Order dictionary as returned from CCXT. + :param trade: Trade object. + :param order: Order object :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - :return bool: When True is returned, then the (long)sell/(short)buy-order is cancelled. + :return bool: When True is returned, then the exit-order is cancelled. """ return self.check_sell_timeout( pair=pair, trade=trade, order=order, current_time=current_time) diff --git a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 index 17dfa0873..d5e2ea8ce 100644 --- a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 +++ b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 @@ -170,7 +170,8 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: """ return True -def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: +def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', + current_time: datetime, **kwargs) -> bool: """ Check entry timeout function callback. This method can be used to override the entry-timeout. @@ -183,14 +184,16 @@ def check_entry_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) When not implemented by a strategy, this simply returns False. :param pair: Pair the trade is for - :param trade: trade object. - :param order: Order dictionary as returned from CCXT. + :param trade: Trade object. + :param order: Order object. + :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - :return bool: When True is returned, then the buy-order is cancelled. + :return bool: When True is returned, then the entry order is cancelled. """ return False -def check_exit_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) -> bool: +def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order', + current_time: datetime, **kwargs) -> bool: """ Check exit timeout function callback. This method can be used to override the exit-timeout. @@ -203,8 +206,9 @@ def check_exit_timeout(self, pair: str, trade: 'Trade', order: dict, **kwargs) - When not implemented by a strategy, this simply returns False. :param pair: Pair the trade is for - :param trade: trade object. - :param order: Order dictionary as returned from CCXT. + :param trade: Trade object. + :param order: Order object. + :param current_time: datetime object, containing the current datetime :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return bool: When True is returned, then the exit-order is cancelled. """ From 26ba899d7d565581763d7509e5a041ad4bcb7160 Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 16 Apr 2022 14:37:36 +0100 Subject: [PATCH 098/254] Add constant, boolean check, rename option to fit with other x_enable, check that RunMode is BACKTEST --- freqtrade/constants.py | 1 + freqtrade/optimize/backtesting.py | 10 ++++------ freqtrade/optimize/optimize_reports.py | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index c6a2ab5d3..d21020a3f 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -380,6 +380,7 @@ CONF_SCHEMA = { }, 'position_adjustment_enable': {'type': 'boolean'}, 'max_entry_position_adjustment': {'type': ['integer', 'number'], 'minimum': -1}, + 'backtest_signal_candle_export_enable': {'type': 'boolean'}, }, 'definitions': { 'exchange': { diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 11704a70b..b8f63d006 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -19,7 +19,7 @@ from freqtrade.data import history from freqtrade.data.btanalysis import find_existing_backtest_stats, trade_list_to_dataframe from freqtrade.data.converter import trim_dataframe, trim_dataframes from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import BacktestState, CandleType, ExitCheckTuple, ExitType, TradingMode +from freqtrade.enums import BacktestState, CandleType, ExitCheckTuple, ExitType, TradingMode, RunMode from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.misc import get_strategy_run_id @@ -74,7 +74,7 @@ class Backtesting: self.strategylist: List[IStrategy] = [] self.all_results: Dict[str, Dict] = {} self.processed_dfs: Dict[str, Dict] = {} - + self._exchange_name = self.config['exchange']['name'] self.exchange = ExchangeResolver.load_exchange(self._exchange_name, self.config) self.dataprovider = DataProvider(self.config, self.exchange) @@ -129,9 +129,7 @@ class Backtesting: self.config['startup_candle_count'] = self.required_startup self.exchange.validate_required_startup_candles(self.required_startup, self.timeframe) - self.enable_backtest_signal_candle_export = False - if self.config.get('enable_backtest_signal_candle_export', None) is not None: - self.enable_backtest_signal_candle_export = bool(self.config.get('enable_backtest_signal_candle_export')) + self.backtest_signal_candle_export_enable = self.config.get('backtest_signal_candle_export_enable', False) self.trading_mode: TradingMode = config.get('trading_mode', TradingMode.SPOT) # strategies which define "can_short=True" will fail to load in Spot mode. @@ -1076,7 +1074,7 @@ class Backtesting: }) self.all_results[self.strategy.get_strategy_name()] = results - if self.enable_backtest_signal_candle_export: + if self.backtest_signal_candle_export_enable and self.dataprovider.runmode == RunMode.BACKTEST: signal_candles_only = {} for pair in preprocessed_tmp.keys(): signal_candles_only_df = DataFrame() diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index c08fa07a1..a97a6cf0f 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -44,6 +44,25 @@ def store_backtest_stats(recordfilename: Path, stats: Dict[str, DataFrame]) -> N latest_filename = Path.joinpath(filename.parent, LAST_BT_RESULT_FN) file_dump_json(latest_filename, {'latest_backtest': str(filename.name)}) +def store_backtest_signal_candles(recordfilename: Path, candles: Dict[str, Dict]) -> None: + """ + Stores backtest trade signal candles + :param recordfilename: Path object, which can either be a filename or a directory. + Filenames will be appended with a timestamp right before the suffix + while for directories, /backtest-result-_signals.pkl will be used as filename + :param stats: Dict containing the backtesting signal candles + """ + if recordfilename.is_dir(): + filename = (recordfilename / + f'backtest-result-{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}_signals.pkl') + else: + filename = Path.joinpath( + recordfilename.parent, + f'{recordfilename.stem}-{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}' + ).with_suffix(recordfilename.suffix) + + with open(filename, 'wb') as f: + pickle.dump(candles, f) def _get_line_floatfmt(stake_currency: str) -> List[str]: """ From 21734c5de77697cb75a5ebee8e52b1b9bce6b10a Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 16 Apr 2022 14:46:30 +0100 Subject: [PATCH 099/254] Add pickle import --- freqtrade/optimize/backtesting.py | 1 + freqtrade/optimize/optimize_reports.py | 1 + 2 files changed, 2 insertions(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index b8f63d006..cc7dbb1fb 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -8,6 +8,7 @@ from collections import defaultdict from copy import deepcopy from datetime import datetime, timedelta, timezone from typing import Any, Dict, List, Optional, Tuple +import pickle from numpy import nan from pandas import DataFrame diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index a97a6cf0f..4265ca70d 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -3,6 +3,7 @@ from copy import deepcopy from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Any, Dict, List, Union +import pickle from numpy import int64 from pandas import DataFrame, to_datetime From 8990ba27099357c7f63540fd05e7c7db98961270 Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 16 Apr 2022 14:49:53 +0100 Subject: [PATCH 100/254] Fix store signal candles --- freqtrade/optimize/backtesting.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index cc7dbb1fb..4ed3f85be 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -27,7 +27,7 @@ from freqtrade.misc import get_strategy_run_id from freqtrade.mixins import LoggingMixin from freqtrade.optimize.bt_progress import BTProgress from freqtrade.optimize.optimize_reports import (generate_backtest_stats, show_backtest_results, - store_backtest_stats) + store_backtest_stats, store_backtest_signal_candles) from freqtrade.persistence import LocalTrade, Order, PairLocks, Trade from freqtrade.plugins.pairlistmanager import PairListManager from freqtrade.plugins.protectionmanager import ProtectionManager @@ -1157,6 +1157,9 @@ class Backtesting: if self.config.get('export', 'none') == 'trades': store_backtest_stats(self.config['exportfilename'], self.results) + if self.enable_backtest_signal_candle_export and self.dataprovider.runmode == RunMode.BACKTEST: + store_backtest_signal_candles(self.config['exportfilename'], self.processed_dfs) + # Results may be mixed up now. Sort them so they follow --strategy-list order. if 'strategy_list' in self.config and len(self.results) > 0: self.results['strategy_comparison'] = sorted( From b1bcf9f33c8157fc1e62fa67532151afaaacb8f2 Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 16 Apr 2022 14:58:17 +0100 Subject: [PATCH 101/254] Fix backtest_enable typo --- freqtrade/optimize/backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 4ed3f85be..f19cd488e 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -1157,7 +1157,7 @@ class Backtesting: if self.config.get('export', 'none') == 'trades': store_backtest_stats(self.config['exportfilename'], self.results) - if self.enable_backtest_signal_candle_export and self.dataprovider.runmode == RunMode.BACKTEST: + if self.backtest_signal_candle_export_enable and self.dataprovider.runmode == RunMode.BACKTEST: store_backtest_signal_candles(self.config['exportfilename'], self.processed_dfs) # Results may be mixed up now. Sort them so they follow --strategy-list order. From f55a9940a7e812f07437e4a207652b8152d7d131 Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 16 Apr 2022 16:15:04 +0100 Subject: [PATCH 102/254] Fix line spacing --- freqtrade/optimize/optimize_reports.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 4265ca70d..f870bd1f5 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -45,6 +45,7 @@ def store_backtest_stats(recordfilename: Path, stats: Dict[str, DataFrame]) -> N latest_filename = Path.joinpath(filename.parent, LAST_BT_RESULT_FN) file_dump_json(latest_filename, {'latest_backtest': str(filename.name)}) + def store_backtest_signal_candles(recordfilename: Path, candles: Dict[str, Dict]) -> None: """ Stores backtest trade signal candles @@ -65,6 +66,7 @@ def store_backtest_signal_candles(recordfilename: Path, candles: Dict[str, Dict] with open(filename, 'wb') as f: pickle.dump(candles, f) + def _get_line_floatfmt(stake_currency: str) -> List[str]: """ Generate floatformat (goes in line with _generate_result_line()) From a63affc5f14156b671c805de9256d3e611d05c93 Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 16 Apr 2022 16:32:04 +0100 Subject: [PATCH 103/254] Fix flake8 complaints --- freqtrade/optimize/backtesting.py | 13 ++++++++----- freqtrade/optimize/optimize_reports.py | 3 ++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index f19cd488e..7e19e26e4 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -8,7 +8,6 @@ from collections import defaultdict from copy import deepcopy from datetime import datetime, timedelta, timezone from typing import Any, Dict, List, Optional, Tuple -import pickle from numpy import nan from pandas import DataFrame @@ -20,14 +19,16 @@ from freqtrade.data import history from freqtrade.data.btanalysis import find_existing_backtest_stats, trade_list_to_dataframe from freqtrade.data.converter import trim_dataframe, trim_dataframes from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import BacktestState, CandleType, ExitCheckTuple, ExitType, TradingMode, RunMode +from freqtrade.enums import (BacktestState, CandleType, ExitCheckTuple, ExitType, TradingMode, + RunMode) from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.misc import get_strategy_run_id from freqtrade.mixins import LoggingMixin from freqtrade.optimize.bt_progress import BTProgress from freqtrade.optimize.optimize_reports import (generate_backtest_stats, show_backtest_results, - store_backtest_stats, store_backtest_signal_candles) + store_backtest_stats, + store_backtest_signal_candles) from freqtrade.persistence import LocalTrade, Order, PairLocks, Trade from freqtrade.plugins.pairlistmanager import PairListManager from freqtrade.plugins.protectionmanager import ProtectionManager @@ -1075,7 +1076,8 @@ class Backtesting: }) self.all_results[self.strategy.get_strategy_name()] = results - if self.backtest_signal_candle_export_enable and self.dataprovider.runmode == RunMode.BACKTEST: + if self.backtest_signal_candle_export_enable and + self.dataprovider.runmode == RunMode.BACKTEST: signal_candles_only = {} for pair in preprocessed_tmp.keys(): signal_candles_only_df = DataFrame() @@ -1157,7 +1159,8 @@ class Backtesting: if self.config.get('export', 'none') == 'trades': store_backtest_stats(self.config['exportfilename'], self.results) - if self.backtest_signal_candle_export_enable and self.dataprovider.runmode == RunMode.BACKTEST: + if self.backtest_signal_candle_export_enable and + self.dataprovider.runmode == RunMode.BACKTEST: store_backtest_signal_candles(self.config['exportfilename'], self.processed_dfs) # Results may be mixed up now. Sort them so they follow --strategy-list order. diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index f870bd1f5..06b393b60 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -51,7 +51,8 @@ def store_backtest_signal_candles(recordfilename: Path, candles: Dict[str, Dict] Stores backtest trade signal candles :param recordfilename: Path object, which can either be a filename or a directory. Filenames will be appended with a timestamp right before the suffix - while for directories, /backtest-result-_signals.pkl will be used as filename + while for directories, /backtest-result-_signals.pkl will be used + as filename :param stats: Dict containing the backtesting signal candles """ if recordfilename.is_dir(): From 7210a1173074915b76751085ff3df2f23b2a55aa Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 16 Apr 2022 16:37:06 +0100 Subject: [PATCH 104/254] Fix flake8 complaints --- freqtrade/optimize/backtesting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 7e19e26e4..839463218 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -131,7 +131,8 @@ class Backtesting: self.config['startup_candle_count'] = self.required_startup self.exchange.validate_required_startup_candles(self.required_startup, self.timeframe) - self.backtest_signal_candle_export_enable = self.config.get('backtest_signal_candle_export_enable', False) + self.backtest_signal_candle_export_enable = self.config.get( + 'backtest_signal_candle_export_enable', False) self.trading_mode: TradingMode = config.get('trading_mode', TradingMode.SPOT) # strategies which define "can_short=True" will fail to load in Spot mode. From b738c4e695e69d51a3827f3f99b42e40f0b01846 Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 16 Apr 2022 16:49:20 +0100 Subject: [PATCH 105/254] Fix flake8 complaints --- freqtrade/optimize/backtesting.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 839463218..e9440f62c 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -20,7 +20,7 @@ from freqtrade.data.btanalysis import find_existing_backtest_stats, trade_list_t from freqtrade.data.converter import trim_dataframe, trim_dataframes from freqtrade.data.dataprovider import DataProvider from freqtrade.enums import (BacktestState, CandleType, ExitCheckTuple, ExitType, TradingMode, - RunMode) + RunMode) from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.misc import get_strategy_run_id @@ -1077,8 +1077,8 @@ class Backtesting: }) self.all_results[self.strategy.get_strategy_name()] = results - if self.backtest_signal_candle_export_enable and - self.dataprovider.runmode == RunMode.BACKTEST: + if self.backtest_signal_candle_export_enable and \ + self.dataprovider.runmode == RunMode.BACKTEST: signal_candles_only = {} for pair in preprocessed_tmp.keys(): signal_candles_only_df = DataFrame() @@ -1160,8 +1160,8 @@ class Backtesting: if self.config.get('export', 'none') == 'trades': store_backtest_stats(self.config['exportfilename'], self.results) - if self.backtest_signal_candle_export_enable and - self.dataprovider.runmode == RunMode.BACKTEST: + if self.backtest_signal_candle_export_enable and \ + self.dataprovider.runmode == RunMode.BACKTEST: store_backtest_signal_candles(self.config['exportfilename'], self.processed_dfs) # Results may be mixed up now. Sort them so they follow --strategy-list order. From 34fb8dacd7fb9083461ad2986b47d2d8480a7e05 Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 16 Apr 2022 17:03:24 +0100 Subject: [PATCH 106/254] Fix isort complaints --- freqtrade/optimize/backtesting.py | 8 ++++---- freqtrade/optimize/optimize_reports.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index e9440f62c..03a6cade0 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -19,16 +19,16 @@ from freqtrade.data import history from freqtrade.data.btanalysis import find_existing_backtest_stats, trade_list_to_dataframe from freqtrade.data.converter import trim_dataframe, trim_dataframes from freqtrade.data.dataprovider import DataProvider -from freqtrade.enums import (BacktestState, CandleType, ExitCheckTuple, ExitType, TradingMode, - RunMode) +from freqtrade.enums import (BacktestState, CandleType, ExitCheckTuple, ExitType, RunMode, + TradingMode) from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.misc import get_strategy_run_id from freqtrade.mixins import LoggingMixin from freqtrade.optimize.bt_progress import BTProgress from freqtrade.optimize.optimize_reports import (generate_backtest_stats, show_backtest_results, - store_backtest_stats, - store_backtest_signal_candles) + store_backtest_signal_candles, + store_backtest_stats) from freqtrade.persistence import LocalTrade, Order, PairLocks, Trade from freqtrade.plugins.pairlistmanager import PairListManager from freqtrade.plugins.protectionmanager import ProtectionManager diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 06b393b60..f0b2e2e71 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -1,9 +1,9 @@ import logging +import pickle from copy import deepcopy from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Any, Dict, List, Union -import pickle from numpy import int64 from pandas import DataFrame, to_datetime From 591a2fb7fc7aa94df8cf38c6fc3dd001a3d02014 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 16 Apr 2022 21:47:44 -0600 Subject: [PATCH 107/254] switch notionalFloor -> minNotional and notionalCap -> maxNotional --- .../exchange/binance_leverage_tiers.json | 8616 ++++++++--------- freqtrade/exchange/exchange.py | 4 +- tests/exchange/test_binance.py | 104 +- tests/exchange/test_ccxt_compat.py | 16 +- tests/exchange/test_exchange.py | 20 +- tests/exchange/test_okx.py | 48 +- 6 files changed, 4404 insertions(+), 4404 deletions(-) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index c0bb965d0..ddffe1250 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -2,90 +2,90 @@ "RAY/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -94,105 +94,105 @@ "SUSHI/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 50000000, + "minNotional": 2000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "2000000", + "maxNotional": "50000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -201,90 +201,90 @@ "CVC/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -293,90 +293,90 @@ "BTS/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -385,90 +385,90 @@ "HOT/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -477,90 +477,90 @@ "ZRX/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -569,90 +569,90 @@ "QTUM/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -661,90 +661,90 @@ "IOTA/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -753,150 +753,150 @@ "BTC/BUSD": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.004, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.004", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.005, "maxLeverage": 25, "info": { "bracket": "2", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.005", "cum": "50.0" } }, { "tier": 3, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.01, "maxLeverage": 20, "info": { "bracket": "3", "initialLeverage": "20", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.01", "cum": "1300.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 7500000, + "minNotional": 1000000, + "maxNotional": 7500000, "maintenanceMarginRate": 0.025, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "7500000", - "notionalFloor": "1000000", + "maxNotional": "7500000", + "minNotional": "1000000", "maintMarginRatio": "0.025", "cum": "16300.0" } }, { "tier": 5, - "notionalFloor": 7500000, - "notionalCap": 40000000, + "minNotional": 7500000, + "maxNotional": 40000000, "maintenanceMarginRate": 0.05, "maxLeverage": 6, "info": { "bracket": "5", "initialLeverage": "6", - "notionalCap": "40000000", - "notionalFloor": "7500000", + "maxNotional": "40000000", + "minNotional": "7500000", "maintMarginRatio": "0.05", "cum": "203800.0" } }, { "tier": 6, - "notionalFloor": 40000000, - "notionalCap": 100000000, + "minNotional": 40000000, + "maxNotional": 100000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "100000000", - "notionalFloor": "40000000", + "maxNotional": "100000000", + "minNotional": "40000000", "maintMarginRatio": "0.1", "cum": "2203800.0" } }, { "tier": 7, - "notionalFloor": 100000000, - "notionalCap": 200000000, + "minNotional": 100000000, + "maxNotional": 200000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "200000000", - "notionalFloor": "100000000", + "maxNotional": "200000000", + "minNotional": "100000000", "maintMarginRatio": "0.125", "cum": "4703800.0" } }, { "tier": 8, - "notionalFloor": 200000000, - "notionalCap": 400000000, + "minNotional": 200000000, + "maxNotional": 400000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "8", "initialLeverage": "3", - "notionalCap": "400000000", - "notionalFloor": "200000000", + "maxNotional": "400000000", + "minNotional": "200000000", "maintMarginRatio": "0.15", "cum": "9703800.0" } }, { "tier": 9, - "notionalFloor": 400000000, - "notionalCap": 600000000, + "minNotional": 400000000, + "maxNotional": 600000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "600000000", - "notionalFloor": "400000000", + "maxNotional": "600000000", + "minNotional": "400000000", "maintMarginRatio": "0.25", "cum": "4.97038E7" } }, { "tier": 10, - "notionalFloor": 600000000, - "notionalCap": 1000000000, + "minNotional": 600000000, + "maxNotional": 1000000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "10", "initialLeverage": "1", - "notionalCap": "1000000000", - "notionalFloor": "600000000", + "maxNotional": "1000000000", + "minNotional": "600000000", "maintMarginRatio": "0.5", "cum": "1.997038E8" } @@ -905,90 +905,90 @@ "WAVES/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -997,135 +997,135 @@ "ADA/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 50000, + "minNotional": 10000, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "10000", + "maxNotional": "50000", + "minNotional": "10000", "maintMarginRatio": "0.01", "cum": "35.0" } }, { "tier": 3, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "535.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8035.0" } }, { "tier": 5, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58035.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108035.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "233035.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1233035.0" } }, { "tier": 9, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6233035.0" } @@ -1134,90 +1134,90 @@ "LIT/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -1226,90 +1226,90 @@ "NU/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -1318,135 +1318,135 @@ "XTZ/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 50000, + "minNotional": 10000, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "10000", + "maxNotional": "50000", + "minNotional": "10000", "maintMarginRatio": "0.01", "cum": "35.0" } }, { "tier": 3, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "535.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8035.0" } }, { "tier": 5, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58035.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108035.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "233035.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1233035.0" } }, { "tier": 9, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6233035.0" } @@ -1455,135 +1455,135 @@ "BNB/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 50000, + "minNotional": 10000, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "10000", + "maxNotional": "50000", + "minNotional": "10000", "maintMarginRatio": "0.01", "cum": "35.0" } }, { "tier": 3, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "535.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8035.0" } }, { "tier": 5, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58035.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108035.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "233035.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1233035.0" } }, { "tier": 9, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6233035.0" } @@ -1592,90 +1592,90 @@ "AKRO/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.012, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.012", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "65.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "690.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5690.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11940.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386940.0" } @@ -1684,90 +1684,90 @@ "HNT/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -1776,135 +1776,135 @@ "ETC/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 50000, + "minNotional": 10000, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "10000", + "maxNotional": "50000", + "minNotional": "10000", "maintMarginRatio": "0.01", "cum": "35.0" } }, { "tier": 3, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "535.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8035.0" } }, { "tier": 5, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58035.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108035.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "233035.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1233035.0" } }, { "tier": 9, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6233035.0" } @@ -1913,135 +1913,135 @@ "XMR/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 50000, + "minNotional": 10000, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "10000", + "maxNotional": "50000", + "minNotional": "10000", "maintMarginRatio": "0.01", "cum": "35.0" } }, { "tier": 3, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "535.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8035.0" } }, { "tier": 5, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58035.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108035.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "233035.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1233035.0" } }, { "tier": 9, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6233035.0" } @@ -2050,90 +2050,90 @@ "YFI/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -2142,90 +2142,90 @@ "FTT/BUSD": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 100000, + "minNotional": 0, + "maxNotional": 100000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "1", "initialLeverage": "20", - "notionalCap": "100000", - "notionalFloor": "0", + "maxNotional": "100000", + "minNotional": "0", "maintMarginRatio": "0.025", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 100000, - "notionalCap": 500000, + "minNotional": 100000, + "maxNotional": 500000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "500000", - "notionalFloor": "100000", + "maxNotional": "500000", + "minNotional": "100000", "maintMarginRatio": "0.05", "cum": "2500.0" } }, { "tier": 3, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.1", "cum": "27500.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "4", "initialLeverage": "3", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.15", "cum": "77500.0" } }, { "tier": 5, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.25", "cum": "277500.0" } }, { "tier": 6, - "notionalFloor": 5000000, - "notionalCap": 30000000, + "minNotional": 5000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "5000000", + "maxNotional": "30000000", + "minNotional": "5000000", "maintMarginRatio": "0.5", "cum": "1527500.0" } @@ -2234,120 +2234,120 @@ "BTCUSDT_210326": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 50000, + "minNotional": 10000, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "10000", + "maxNotional": "50000", + "minNotional": "10000", "maintMarginRatio": "0.01", "cum": "35.0" } }, { "tier": 3, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "535.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8035.0" } }, { "tier": 5, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58035.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108035.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "233035.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 9223372036854776000, + "minNotional": 10000000, + "maxNotional": 9223372036854776000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "9223372036854775807", - "notionalFloor": "10000000", + "maxNotional": "9223372036854775807", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1233035.0" } @@ -2356,150 +2356,150 @@ "ETH/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.005, "maxLeverage": 100, "info": { "bracket": "1", "initialLeverage": "100", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.005", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 100000, + "minNotional": 10000, + "maxNotional": 100000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "2", "initialLeverage": "75", - "notionalCap": "100000", - "notionalFloor": "10000", + "maxNotional": "100000", + "minNotional": "10000", "maintMarginRatio": "0.0065", "cum": "15.0" } }, { "tier": 3, - "notionalFloor": 100000, - "notionalCap": 500000, + "minNotional": 100000, + "maxNotional": 500000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "3", "initialLeverage": "50", - "notionalCap": "500000", - "notionalFloor": "100000", + "maxNotional": "500000", + "minNotional": "100000", "maintMarginRatio": "0.01", "cum": "365.0" } }, { "tier": 4, - "notionalFloor": 500000, - "notionalCap": 1500000, + "minNotional": 500000, + "maxNotional": 1500000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "4", "initialLeverage": "25", - "notionalCap": "1500000", - "notionalFloor": "500000", + "maxNotional": "1500000", + "minNotional": "500000", "maintMarginRatio": "0.02", "cum": "5365.0" } }, { "tier": 5, - "notionalFloor": 1500000, - "notionalCap": 4000000, + "minNotional": 1500000, + "maxNotional": 4000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "4000000", - "notionalFloor": "1500000", + "maxNotional": "4000000", + "minNotional": "1500000", "maintMarginRatio": "0.05", "cum": "50365.0" } }, { "tier": 6, - "notionalFloor": 4000000, - "notionalCap": 10000000, + "minNotional": 4000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "10000000", - "notionalFloor": "4000000", + "maxNotional": "10000000", + "minNotional": "4000000", "maintMarginRatio": "0.1", "cum": "250365.0" } }, { "tier": 7, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.125", "cum": "500365.0" } }, { "tier": 8, - "notionalFloor": 20000000, - "notionalCap": 40000000, + "minNotional": 20000000, + "maxNotional": 40000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "8", "initialLeverage": "3", - "notionalCap": "40000000", - "notionalFloor": "20000000", + "maxNotional": "40000000", + "minNotional": "20000000", "maintMarginRatio": "0.15", "cum": "1000365.0" } }, { "tier": 9, - "notionalFloor": 40000000, - "notionalCap": 150000000, + "minNotional": 40000000, + "maxNotional": 150000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "150000000", - "notionalFloor": "40000000", + "maxNotional": "150000000", + "minNotional": "40000000", "maintMarginRatio": "0.25", "cum": "5000365.0" } }, { "tier": 10, - "notionalFloor": 150000000, - "notionalCap": 500000000, + "minNotional": 150000000, + "maxNotional": 500000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "10", "initialLeverage": "1", - "notionalCap": "500000000", - "notionalFloor": "150000000", + "maxNotional": "500000000", + "minNotional": "150000000", "maintMarginRatio": "0.5", "cum": "4.2500365E7" } @@ -2508,105 +2508,105 @@ "ALICE/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -2615,90 +2615,90 @@ "ALPHA/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -2707,90 +2707,90 @@ "SFP/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -2799,90 +2799,90 @@ "REEF/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -2891,90 +2891,90 @@ "BAT/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -2983,105 +2983,105 @@ "DOGE/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "7000.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "57000.0" } }, { "tier": 5, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "107000.0" } }, { "tier": 6, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.25", "cum": "732000.0" } }, { "tier": 7, - "notionalFloor": 10000000, - "notionalCap": 50000000, + "minNotional": 10000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "10000000", + "maxNotional": "50000000", + "minNotional": "10000000", "maintMarginRatio": "0.5", "cum": "3232000.0" } @@ -3090,135 +3090,135 @@ "TRX/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 50000, + "minNotional": 10000, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "10000", + "maxNotional": "50000", + "minNotional": "10000", "maintMarginRatio": "0.01", "cum": "35.0" } }, { "tier": 3, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "535.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8035.0" } }, { "tier": 5, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58035.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108035.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "233035.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1233035.0" } }, { "tier": 9, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6233035.0" } @@ -3227,90 +3227,90 @@ "RLC/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -3319,90 +3319,90 @@ "DOTECOUSDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.012, "maxLeverage": 20, "info": { "bracket": "1", "initialLeverage": "20", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.012", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "65.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "690.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 4, "info": { "bracket": "4", "initialLeverage": "4", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5690.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11940.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 9223372036854776000, + "minNotional": 1000000, + "maxNotional": 9223372036854776000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "9223372036854775807", - "notionalFloor": "1000000", + "maxNotional": "9223372036854775807", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386940.0" } @@ -3411,90 +3411,90 @@ "BTCSTUSDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 9223372036854776000, + "minNotional": 1000000, + "maxNotional": 9223372036854776000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "9223372036854775807", - "notionalFloor": "1000000", + "maxNotional": "9223372036854775807", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -3503,90 +3503,90 @@ "STORJ/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -3595,90 +3595,90 @@ "SNX/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -3687,90 +3687,90 @@ "ETHUSDT_210625": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 250000, + "minNotional": 0, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "0", + "maxNotional": "250000", + "minNotional": "0", "maintMarginRatio": "0.02", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "7500.0" } }, { "tier": 3, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "57500.0" } }, { "tier": 4, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "4", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "107500.0" } }, { "tier": 5, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "5", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "232500.0" } }, { "tier": 6, - "notionalFloor": 10000000, - "notionalCap": 9223372036854776000, + "minNotional": 10000000, + "maxNotional": 9223372036854776000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "9223372036854775807", - "notionalFloor": "10000000", + "maxNotional": "9223372036854775807", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1232500.0" } @@ -3779,90 +3779,90 @@ "1000XEC/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -3871,90 +3871,90 @@ "AUDIO/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -3963,135 +3963,135 @@ "XLM/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 50000, + "minNotional": 10000, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "10000", + "maxNotional": "50000", + "minNotional": "10000", "maintMarginRatio": "0.01", "cum": "35.0" } }, { "tier": 3, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "535.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8035.0" } }, { "tier": 5, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58035.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108035.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "233035.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1233035.0" } }, { "tier": 9, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6233035.0" } @@ -4100,135 +4100,135 @@ "BTCBUSD_210129": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.004, "maxLeverage": 20, "info": { "bracket": "1", "initialLeverage": "20", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.004", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.005, "maxLeverage": 15, "info": { "bracket": "2", "initialLeverage": "15", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.005", "cum": "5.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.01, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.01", "cum": "130.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 500000, + "minNotional": 100000, + "maxNotional": 500000, "maintenanceMarginRate": 0.025, "maxLeverage": 7, "info": { "bracket": "4", "initialLeverage": "7", - "notionalCap": "500000", - "notionalFloor": "100000", + "maxNotional": "500000", + "minNotional": "100000", "maintMarginRatio": "0.025", "cum": "1630.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 2000000, + "minNotional": 500000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.05, "maxLeverage": 6, "info": { "bracket": "5", "initialLeverage": "6", - "notionalCap": "2000000", - "notionalFloor": "500000", + "maxNotional": "2000000", + "minNotional": "500000", "maintMarginRatio": "0.05", "cum": "14130.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.1", "cum": "114130.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.125", "cum": "239130.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "8", "initialLeverage": "3", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.15", "cum": "489130.0" } }, { "tier": 9, - "notionalFloor": 20000000, - "notionalCap": 9223372036854776000, + "minNotional": 20000000, + "maxNotional": 9223372036854776000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "9223372036854775807", - "notionalFloor": "20000000", + "maxNotional": "9223372036854775807", + "minNotional": "20000000", "maintMarginRatio": "0.25", "cum": "2489130.0" } @@ -4237,90 +4237,90 @@ "IOTX/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -4329,90 +4329,90 @@ "NEO/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -4421,90 +4421,90 @@ "UNFI/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -4513,105 +4513,105 @@ "SAND/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -4620,90 +4620,90 @@ "DASH/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -4712,90 +4712,90 @@ "KAVA/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -4804,90 +4804,90 @@ "RUNE/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -4896,90 +4896,90 @@ "CTK/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -4988,135 +4988,135 @@ "LINK/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 50000, + "minNotional": 10000, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "10000", + "maxNotional": "50000", + "minNotional": "10000", "maintMarginRatio": "0.01", "cum": "35.0" } }, { "tier": 3, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "535.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8035.0" } }, { "tier": 5, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58035.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108035.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "233035.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1233035.0" } }, { "tier": 9, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6233035.0" } @@ -5125,105 +5125,105 @@ "CELR/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -5232,90 +5232,90 @@ "RSR/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -5324,90 +5324,90 @@ "ADA/BUSD": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 100000, + "minNotional": 0, + "maxNotional": 100000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "1", "initialLeverage": "20", - "notionalCap": "100000", - "notionalFloor": "0", + "maxNotional": "100000", + "minNotional": "0", "maintMarginRatio": "0.025", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 100000, - "notionalCap": 500000, + "minNotional": 100000, + "maxNotional": 500000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "500000", - "notionalFloor": "100000", + "maxNotional": "500000", + "minNotional": "100000", "maintMarginRatio": "0.05", "cum": "2500.0" } }, { "tier": 3, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.1", "cum": "27500.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "4", "initialLeverage": "3", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.15", "cum": "77500.0" } }, { "tier": 5, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.25", "cum": "277500.0" } }, { "tier": 6, - "notionalFloor": 5000000, - "notionalCap": 30000000, + "minNotional": 5000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "5000000", + "maxNotional": "30000000", + "minNotional": "5000000", "maintMarginRatio": "0.5", "cum": "1527500.0" } @@ -5416,90 +5416,90 @@ "DGB/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -5508,90 +5508,90 @@ "SKL/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -5600,90 +5600,90 @@ "REN/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -5692,90 +5692,90 @@ "LPT/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -5784,90 +5784,90 @@ "TOMO/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -5876,90 +5876,90 @@ "MTL/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -5968,135 +5968,135 @@ "LTC/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 50000, + "minNotional": 10000, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "10000", + "maxNotional": "50000", + "minNotional": "10000", "maintMarginRatio": "0.01", "cum": "35.0" } }, { "tier": 3, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "535.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8035.0" } }, { "tier": 5, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58035.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108035.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "233035.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1233035.0" } }, { "tier": 9, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6233035.0" } @@ -6105,90 +6105,90 @@ "DODO/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -6197,90 +6197,90 @@ "EGLD/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 50000000, + "minNotional": 1000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "1000000", + "maxNotional": "50000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -6289,90 +6289,90 @@ "KSM/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -6381,90 +6381,90 @@ "BNB/BUSD": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 100000, + "minNotional": 0, + "maxNotional": 100000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "1", "initialLeverage": "20", - "notionalCap": "100000", - "notionalFloor": "0", + "maxNotional": "100000", + "minNotional": "0", "maintMarginRatio": "0.025", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 100000, - "notionalCap": 500000, + "minNotional": 100000, + "maxNotional": 500000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "500000", - "notionalFloor": "100000", + "maxNotional": "500000", + "minNotional": "100000", "maintMarginRatio": "0.05", "cum": "2500.0" } }, { "tier": 3, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.1", "cum": "27500.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "4", "initialLeverage": "3", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.15", "cum": "77500.0" } }, { "tier": 5, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.25", "cum": "277500.0" } }, { "tier": 6, - "notionalFloor": 5000000, - "notionalCap": 30000000, + "minNotional": 5000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "5000000", + "maxNotional": "30000000", + "minNotional": "5000000", "maintMarginRatio": "0.5", "cum": "1527500.0" } @@ -6473,90 +6473,90 @@ "BTCUSDT_210625": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 250000, + "minNotional": 0, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "0", + "maxNotional": "250000", + "minNotional": "0", "maintMarginRatio": "0.02", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "7500.0" } }, { "tier": 3, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "57500.0" } }, { "tier": 4, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "4", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "107500.0" } }, { "tier": 5, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "5", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "232500.0" } }, { "tier": 6, - "notionalFloor": 10000000, - "notionalCap": 9223372036854776000, + "minNotional": 10000000, + "maxNotional": 9223372036854776000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "9223372036854775807", - "notionalFloor": "10000000", + "maxNotional": "9223372036854775807", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1232500.0" } @@ -6565,90 +6565,90 @@ "ONT/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -6657,105 +6657,105 @@ "VET/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -6764,90 +6764,90 @@ "TRB/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -6856,105 +6856,105 @@ "MANA/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -6963,90 +6963,90 @@ "COTI/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -7055,90 +7055,90 @@ "CHR/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -7147,105 +7147,105 @@ "ETHUSDT_210924": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 250000, + "minNotional": 0, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "0", + "maxNotional": "250000", + "minNotional": "0", "maintMarginRatio": "0.02", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "7500.0" } }, { "tier": 3, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "57500.0" } }, { "tier": 4, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "4", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "107500.0" } }, { "tier": 5, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "5", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "232500.0" } }, { "tier": 6, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1232500.0" } }, { "tier": 7, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6232500.0" } @@ -7254,90 +7254,90 @@ "BAKE/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -7346,90 +7346,90 @@ "GRT/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -7438,105 +7438,105 @@ "ETHUSDT_220325": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 375000, + "minNotional": 0, + "maxNotional": 375000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "375000", - "notionalFloor": "0", + "maxNotional": "375000", + "minNotional": "0", "maintMarginRatio": "0.02", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 375000, - "notionalCap": 2000000, + "minNotional": 375000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "2000000", - "notionalFloor": "375000", + "maxNotional": "2000000", + "minNotional": "375000", "maintMarginRatio": "0.05", "cum": "11250.0" } }, { "tier": 3, - "notionalFloor": 2000000, - "notionalCap": 4000000, + "minNotional": 2000000, + "maxNotional": 4000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "maxNotional": "4000000", + "minNotional": "2000000", "maintMarginRatio": "0.1", "cum": "111250.0" } }, { "tier": 4, - "notionalFloor": 4000000, - "notionalCap": 10000000, + "minNotional": 4000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "4", "initialLeverage": "4", - "notionalCap": "10000000", - "notionalFloor": "4000000", + "maxNotional": "10000000", + "minNotional": "4000000", "maintMarginRatio": "0.125", "cum": "211250.0" } }, { "tier": 5, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "5", "initialLeverage": "3", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.15", "cum": "461250.0" } }, { "tier": 6, - "notionalFloor": 20000000, - "notionalCap": 40000000, + "minNotional": 20000000, + "maxNotional": 40000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "40000000", - "notionalFloor": "20000000", + "maxNotional": "40000000", + "minNotional": "20000000", "maintMarginRatio": "0.25", "cum": "2461250.0" } }, { "tier": 7, - "notionalFloor": 40000000, - "notionalCap": 400000000, + "minNotional": 40000000, + "maxNotional": 400000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "400000000", - "notionalFloor": "40000000", + "maxNotional": "400000000", + "minNotional": "40000000", "maintMarginRatio": "0.5", "cum": "1.246125E7" } @@ -7545,90 +7545,90 @@ "FLM/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -7637,90 +7637,90 @@ "MASK/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -7729,135 +7729,135 @@ "EOS/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 50000, + "minNotional": 10000, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "10000", + "maxNotional": "50000", + "minNotional": "10000", "maintMarginRatio": "0.01", "cum": "35.0" } }, { "tier": 3, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "535.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8035.0" } }, { "tier": 5, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58035.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108035.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "233035.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1233035.0" } }, { "tier": 9, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6233035.0" } @@ -7866,105 +7866,105 @@ "ETHUSDT_211231": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 375000, + "minNotional": 0, + "maxNotional": 375000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "375000", - "notionalFloor": "0", + "maxNotional": "375000", + "minNotional": "0", "maintMarginRatio": "0.02", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 375000, - "notionalCap": 2000000, + "minNotional": 375000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "2000000", - "notionalFloor": "375000", + "maxNotional": "2000000", + "minNotional": "375000", "maintMarginRatio": "0.05", "cum": "11250.0" } }, { "tier": 3, - "notionalFloor": 2000000, - "notionalCap": 4000000, + "minNotional": 2000000, + "maxNotional": 4000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "maxNotional": "4000000", + "minNotional": "2000000", "maintMarginRatio": "0.1", "cum": "111250.0" } }, { "tier": 4, - "notionalFloor": 4000000, - "notionalCap": 10000000, + "minNotional": 4000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "4", "initialLeverage": "4", - "notionalCap": "10000000", - "notionalFloor": "4000000", + "maxNotional": "10000000", + "minNotional": "4000000", "maintMarginRatio": "0.125", "cum": "211250.0" } }, { "tier": 5, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "5", "initialLeverage": "3", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.15", "cum": "461250.0" } }, { "tier": 6, - "notionalFloor": 20000000, - "notionalCap": 40000000, + "minNotional": 20000000, + "maxNotional": 40000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "40000000", - "notionalFloor": "20000000", + "maxNotional": "40000000", + "minNotional": "20000000", "maintMarginRatio": "0.25", "cum": "2461250.0" } }, { "tier": 7, - "notionalFloor": 40000000, - "notionalCap": 400000000, + "minNotional": 40000000, + "maxNotional": 400000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "400000000", - "notionalFloor": "40000000", + "maxNotional": "400000000", + "minNotional": "40000000", "maintMarginRatio": "0.5", "cum": "1.246125E7" } @@ -7973,90 +7973,90 @@ "OGN/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -8065,90 +8065,90 @@ "SC/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -8157,90 +8157,90 @@ "BAL/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -8249,90 +8249,90 @@ "STMX/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -8341,90 +8341,90 @@ "BTTUSDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -8433,120 +8433,120 @@ "LUNA/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "2", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "500.0" } }, { "tier": 3, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8000.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58000.0" } }, { "tier": 5, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108000.0" } }, { "tier": 6, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.1665, "maxLeverage": 3, "info": { "bracket": "6", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.1665", "cum": "315500.0" } }, { "tier": 7, - "notionalFloor": 10000000, - "notionalCap": 15000000, + "minNotional": 10000000, + "maxNotional": 15000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "15000000", - "notionalFloor": "10000000", + "maxNotional": "15000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1150500.0" } }, { "tier": 8, - "notionalFloor": 15000000, - "notionalCap": 50000000, + "minNotional": 15000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "15000000", + "maxNotional": "50000000", + "minNotional": "15000000", "maintMarginRatio": "0.5", "cum": "4900500.0" } @@ -8555,90 +8555,90 @@ "DENT/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -8647,90 +8647,90 @@ "1000BTTC/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -8739,90 +8739,90 @@ "KNC/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -8831,90 +8831,90 @@ "SRM/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -8923,105 +8923,105 @@ "ENJ/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -9030,90 +9030,90 @@ "C98/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -9122,90 +9122,90 @@ "ZEN/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -9214,105 +9214,105 @@ "ATOM/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -9321,105 +9321,105 @@ "NEAR/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -9428,90 +9428,90 @@ "SOL/BUSD": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 100000, + "minNotional": 0, + "maxNotional": 100000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "1", "initialLeverage": "20", - "notionalCap": "100000", - "notionalFloor": "0", + "maxNotional": "100000", + "minNotional": "0", "maintMarginRatio": "0.025", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 100000, - "notionalCap": 500000, + "minNotional": 100000, + "maxNotional": 500000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "500000", - "notionalFloor": "100000", + "maxNotional": "500000", + "minNotional": "100000", "maintMarginRatio": "0.05", "cum": "2500.0" } }, { "tier": 3, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.1", "cum": "27500.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "4", "initialLeverage": "3", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.15", "cum": "77500.0" } }, { "tier": 5, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.25", "cum": "277500.0" } }, { "tier": 6, - "notionalFloor": 5000000, - "notionalCap": 30000000, + "minNotional": 5000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "5000000", + "maxNotional": "30000000", + "minNotional": "5000000", "maintMarginRatio": "0.5", "cum": "1527500.0" } @@ -9520,90 +9520,90 @@ "ENS/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -9612,135 +9612,135 @@ "BCH/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 50000, + "minNotional": 10000, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "10000", + "maxNotional": "50000", + "minNotional": "10000", "maintMarginRatio": "0.01", "cum": "35.0" } }, { "tier": 3, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "535.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8035.0" } }, { "tier": 5, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58035.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108035.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "233035.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1233035.0" } }, { "tier": 9, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6233035.0" } @@ -9749,90 +9749,90 @@ "ATA/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -9841,90 +9841,90 @@ "IOST/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -9933,90 +9933,90 @@ "HBAR/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -10025,105 +10025,105 @@ "ZEC/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -10132,105 +10132,105 @@ "1000SHIB/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -10239,90 +10239,90 @@ "TLM/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -10331,90 +10331,90 @@ "ANT/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -10423,90 +10423,90 @@ "BZRXUSDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -10515,150 +10515,150 @@ "ETH/BUSD": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 25000, + "minNotional": 0, + "maxNotional": 25000, "maintenanceMarginRate": 0.004, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "25000", - "notionalFloor": "0", + "maxNotional": "25000", + "minNotional": "0", "maintMarginRatio": "0.004", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.005, "maxLeverage": 25, "info": { "bracket": "2", "initialLeverage": "25", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.005", "cum": "25.0" } }, { "tier": 3, - "notionalFloor": 100000, - "notionalCap": 500000, + "minNotional": 100000, + "maxNotional": 500000, "maintenanceMarginRate": 0.01, "maxLeverage": 20, "info": { "bracket": "3", "initialLeverage": "20", - "notionalCap": "500000", - "notionalFloor": "100000", + "maxNotional": "500000", + "minNotional": "100000", "maintMarginRatio": "0.01", "cum": "525.0" } }, { "tier": 4, - "notionalFloor": 500000, - "notionalCap": 1500000, + "minNotional": 500000, + "maxNotional": 1500000, "maintenanceMarginRate": 0.025, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1500000", - "notionalFloor": "500000", + "maxNotional": "1500000", + "minNotional": "500000", "maintMarginRatio": "0.025", "cum": "8025.0" } }, { "tier": 5, - "notionalFloor": 1500000, - "notionalCap": 4000000, + "minNotional": 1500000, + "maxNotional": 4000000, "maintenanceMarginRate": 0.05, "maxLeverage": 6, "info": { "bracket": "5", "initialLeverage": "6", - "notionalCap": "4000000", - "notionalFloor": "1500000", + "maxNotional": "4000000", + "minNotional": "1500000", "maintMarginRatio": "0.05", "cum": "45525.0" } }, { "tier": 6, - "notionalFloor": 4000000, - "notionalCap": 10000000, + "minNotional": 4000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "10000000", - "notionalFloor": "4000000", + "maxNotional": "10000000", + "minNotional": "4000000", "maintMarginRatio": "0.1", "cum": "245525.0" } }, { "tier": 7, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.125", "cum": "495525.0" } }, { "tier": 8, - "notionalFloor": 20000000, - "notionalCap": 40000000, + "minNotional": 20000000, + "maxNotional": 40000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "8", "initialLeverage": "3", - "notionalCap": "40000000", - "notionalFloor": "20000000", + "maxNotional": "40000000", + "minNotional": "20000000", "maintMarginRatio": "0.15", "cum": "995525.0" } }, { "tier": 9, - "notionalFloor": 40000000, - "notionalCap": 150000000, + "minNotional": 40000000, + "maxNotional": 150000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "150000000", - "notionalFloor": "40000000", + "maxNotional": "150000000", + "minNotional": "40000000", "maintMarginRatio": "0.25", "cum": "4995525.0" } }, { "tier": 10, - "notionalFloor": 150000000, - "notionalCap": 500000000, + "minNotional": 150000000, + "maxNotional": 500000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "10", "initialLeverage": "1", - "notionalCap": "500000000", - "notionalFloor": "150000000", + "maxNotional": "500000000", + "minNotional": "150000000", "maintMarginRatio": "0.5", "cum": "4.2495525E7" } @@ -10667,105 +10667,105 @@ "GALA/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -10774,120 +10774,120 @@ "AAVE/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "2", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "500.0" } }, { "tier": 3, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8000.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58000.0" } }, { "tier": 5, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108000.0" } }, { "tier": 6, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.1665, "maxLeverage": 3, "info": { "bracket": "6", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.1665", "cum": "315500.0" } }, { "tier": 7, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1150500.0" } }, { "tier": 8, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6150500.0" } @@ -10896,90 +10896,90 @@ "GTC/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -10988,105 +10988,105 @@ "ALGO/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -11095,90 +11095,90 @@ "ICP/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -11187,105 +11187,105 @@ "BTCUSDT_210924": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 250000, + "minNotional": 0, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "0", + "maxNotional": "250000", + "minNotional": "0", "maintMarginRatio": "0.02", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "7500.0" } }, { "tier": 3, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "57500.0" } }, { "tier": 4, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "4", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "107500.0" } }, { "tier": 5, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "5", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "232500.0" } }, { "tier": 6, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1232500.0" } }, { "tier": 7, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6232500.0" } @@ -11294,105 +11294,105 @@ "LRC/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -11401,105 +11401,105 @@ "AVAX/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 750000, + "minNotional": 500000, + "maxNotional": 750000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "750000", - "notionalFloor": "500000", + "maxNotional": "750000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 750000, - "notionalCap": 1000000, + "minNotional": 750000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "750000", + "maxNotional": "1000000", + "minNotional": "750000", "maintMarginRatio": "0.25", "cum": "123250.0" } }, { "tier": 7, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "373250.0" } @@ -11508,105 +11508,105 @@ "BTCUSDT_220325": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 375000, + "minNotional": 0, + "maxNotional": 375000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "375000", - "notionalFloor": "0", + "maxNotional": "375000", + "minNotional": "0", "maintMarginRatio": "0.02", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 375000, - "notionalCap": 2000000, + "minNotional": 375000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "2000000", - "notionalFloor": "375000", + "maxNotional": "2000000", + "minNotional": "375000", "maintMarginRatio": "0.05", "cum": "11250.0" } }, { "tier": 3, - "notionalFloor": 2000000, - "notionalCap": 4000000, + "minNotional": 2000000, + "maxNotional": 4000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "maxNotional": "4000000", + "minNotional": "2000000", "maintMarginRatio": "0.1", "cum": "111250.0" } }, { "tier": 4, - "notionalFloor": 4000000, - "notionalCap": 10000000, + "minNotional": 4000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "4", "initialLeverage": "4", - "notionalCap": "10000000", - "notionalFloor": "4000000", + "maxNotional": "10000000", + "minNotional": "4000000", "maintMarginRatio": "0.125", "cum": "211250.0" } }, { "tier": 5, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "5", "initialLeverage": "3", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.15", "cum": "461250.0" } }, { "tier": 6, - "notionalFloor": 20000000, - "notionalCap": 40000000, + "minNotional": 20000000, + "maxNotional": 40000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "40000000", - "notionalFloor": "20000000", + "maxNotional": "40000000", + "minNotional": "20000000", "maintMarginRatio": "0.25", "cum": "2461250.0" } }, { "tier": 7, - "notionalFloor": 40000000, - "notionalCap": 400000000, + "minNotional": 40000000, + "maxNotional": 400000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "400000000", - "notionalFloor": "40000000", + "maxNotional": "400000000", + "minNotional": "40000000", "maintMarginRatio": "0.5", "cum": "1.246125E7" } @@ -11615,90 +11615,90 @@ "ARPA/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -11707,90 +11707,90 @@ "CELO/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -11799,90 +11799,90 @@ "ROSE/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -11891,105 +11891,105 @@ "MATIC/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 750000, + "minNotional": 500000, + "maxNotional": 750000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "750000", - "notionalFloor": "500000", + "maxNotional": "750000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 750000, - "notionalCap": 1000000, + "minNotional": 750000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "750000", + "maxNotional": "1000000", + "minNotional": "750000", "maintMarginRatio": "0.25", "cum": "123250.0" } }, { "tier": 7, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "373250.0" } @@ -11998,90 +11998,90 @@ "1INCH/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.012, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.012", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "65.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "690.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5690.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11940.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 100000000, + "minNotional": 1000000, + "maxNotional": 100000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "100000000", - "notionalFloor": "1000000", + "maxNotional": "100000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386940.0" } @@ -12090,90 +12090,90 @@ "MKR/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -12182,90 +12182,90 @@ "PEOPLE/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -12274,120 +12274,120 @@ "THETA/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "2", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "500.0" } }, { "tier": 3, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8000.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58000.0" } }, { "tier": 5, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108000.0" } }, { "tier": 6, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.1665, "maxLeverage": 3, "info": { "bracket": "6", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.1665", "cum": "315500.0" } }, { "tier": 7, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1150500.0" } }, { "tier": 8, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6150500.0" } @@ -12396,120 +12396,120 @@ "UNI/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "2", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "500.0" } }, { "tier": 3, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8000.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58000.0" } }, { "tier": 5, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108000.0" } }, { "tier": 6, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.1665, "maxLeverage": 3, "info": { "bracket": "6", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.1665", "cum": "315500.0" } }, { "tier": 7, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1150500.0" } }, { "tier": 8, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6150500.0" } @@ -12518,120 +12518,120 @@ "ETHUSDT_210326": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 50000, + "minNotional": 10000, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "10000", + "maxNotional": "50000", + "minNotional": "10000", "maintMarginRatio": "0.01", "cum": "35.0" } }, { "tier": 3, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "535.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8035.0" } }, { "tier": 5, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58035.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108035.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "233035.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 9223372036854776000, + "minNotional": 10000000, + "maxNotional": 9223372036854776000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "9223372036854775807", - "notionalFloor": "10000000", + "maxNotional": "9223372036854775807", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1233035.0" } @@ -12640,90 +12640,90 @@ "LINA/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -12732,90 +12732,90 @@ "AR/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -12824,90 +12824,90 @@ "RVN/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -12916,120 +12916,120 @@ "FIL/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "2", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "500.0" } }, { "tier": 3, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8000.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58000.0" } }, { "tier": 5, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108000.0" } }, { "tier": 6, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.1665, "maxLeverage": 3, "info": { "bracket": "6", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.1665", "cum": "315500.0" } }, { "tier": 7, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1150500.0" } }, { "tier": 8, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6150500.0" } @@ -13038,90 +13038,90 @@ "NKN/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -13130,90 +13130,90 @@ "KLAY/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -13222,90 +13222,90 @@ "DEFI/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -13314,90 +13314,90 @@ "COMP/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -13406,90 +13406,90 @@ "BTCDOM/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -13498,105 +13498,105 @@ "SOL/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "7000.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "57000.0" } }, { "tier": 5, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "107000.0" } }, { "tier": 6, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.25", "cum": "732000.0" } }, { "tier": 7, - "notionalFloor": 10000000, - "notionalCap": 50000000, + "minNotional": 10000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "10000000", + "maxNotional": "50000000", + "minNotional": "10000000", "maintMarginRatio": "0.5", "cum": "3232000.0" } @@ -13605,150 +13605,150 @@ "BTC/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.004, "maxLeverage": 125, "info": { "bracket": "1", "initialLeverage": "125", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.004", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.005, "maxLeverage": 100, "info": { "bracket": "2", "initialLeverage": "100", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.005", "cum": "50.0" } }, { "tier": 3, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "3", "initialLeverage": "50", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.01", "cum": "1300.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 7500000, + "minNotional": 1000000, + "maxNotional": 7500000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "4", "initialLeverage": "20", - "notionalCap": "7500000", - "notionalFloor": "1000000", + "maxNotional": "7500000", + "minNotional": "1000000", "maintMarginRatio": "0.025", "cum": "16300.0" } }, { "tier": 5, - "notionalFloor": 7500000, - "notionalCap": 40000000, + "minNotional": 7500000, + "maxNotional": 40000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "5", "initialLeverage": "10", - "notionalCap": "40000000", - "notionalFloor": "7500000", + "maxNotional": "40000000", + "minNotional": "7500000", "maintMarginRatio": "0.05", "cum": "203800.0" } }, { "tier": 6, - "notionalFloor": 40000000, - "notionalCap": 100000000, + "minNotional": 40000000, + "maxNotional": 100000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "100000000", - "notionalFloor": "40000000", + "maxNotional": "100000000", + "minNotional": "40000000", "maintMarginRatio": "0.1", "cum": "2203800.0" } }, { "tier": 7, - "notionalFloor": 100000000, - "notionalCap": 200000000, + "minNotional": 100000000, + "maxNotional": 200000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "200000000", - "notionalFloor": "100000000", + "maxNotional": "200000000", + "minNotional": "100000000", "maintMarginRatio": "0.125", "cum": "4703800.0" } }, { "tier": 8, - "notionalFloor": 200000000, - "notionalCap": 400000000, + "minNotional": 200000000, + "maxNotional": 400000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "8", "initialLeverage": "3", - "notionalCap": "400000000", - "notionalFloor": "200000000", + "maxNotional": "400000000", + "minNotional": "200000000", "maintMarginRatio": "0.15", "cum": "9703800.0" } }, { "tier": 9, - "notionalFloor": 400000000, - "notionalCap": 600000000, + "minNotional": 400000000, + "maxNotional": 600000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "600000000", - "notionalFloor": "400000000", + "maxNotional": "600000000", + "minNotional": "400000000", "maintMarginRatio": "0.25", "cum": "4.97038E7" } }, { "tier": 10, - "notionalFloor": 600000000, - "notionalCap": 1000000000, + "minNotional": 600000000, + "maxNotional": 1000000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "10", "initialLeverage": "1", - "notionalCap": "1000000000", - "notionalFloor": "600000000", + "maxNotional": "1000000000", + "minNotional": "600000000", "maintMarginRatio": "0.5", "cum": "1.997038E8" } @@ -13757,90 +13757,90 @@ "OMG/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.024, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.024", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "5.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "630.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5630.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11880.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 10000000, + "minNotional": 1000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "10000000", - "notionalFloor": "1000000", + "maxNotional": "10000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386880.0" } @@ -13849,90 +13849,90 @@ "ICX/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -13941,90 +13941,90 @@ "BLZ/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -14033,105 +14033,105 @@ "BTCUSDT_211231": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 375000, + "minNotional": 0, + "maxNotional": 375000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "375000", - "notionalFloor": "0", + "maxNotional": "375000", + "minNotional": "0", "maintMarginRatio": "0.02", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 375000, - "notionalCap": 2000000, + "minNotional": 375000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "2000000", - "notionalFloor": "375000", + "maxNotional": "2000000", + "minNotional": "375000", "maintMarginRatio": "0.05", "cum": "11250.0" } }, { "tier": 3, - "notionalFloor": 2000000, - "notionalCap": 4000000, + "minNotional": 2000000, + "maxNotional": 4000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "4000000", - "notionalFloor": "2000000", + "maxNotional": "4000000", + "minNotional": "2000000", "maintMarginRatio": "0.1", "cum": "111250.0" } }, { "tier": 4, - "notionalFloor": 4000000, - "notionalCap": 10000000, + "minNotional": 4000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "4", "initialLeverage": "4", - "notionalCap": "10000000", - "notionalFloor": "4000000", + "maxNotional": "10000000", + "minNotional": "4000000", "maintMarginRatio": "0.125", "cum": "211250.0" } }, { "tier": 5, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "5", "initialLeverage": "3", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.15", "cum": "461250.0" } }, { "tier": 6, - "notionalFloor": 20000000, - "notionalCap": 40000000, + "minNotional": 20000000, + "maxNotional": 40000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "40000000", - "notionalFloor": "20000000", + "maxNotional": "40000000", + "minNotional": "20000000", "maintMarginRatio": "0.25", "cum": "2461250.0" } }, { "tier": 7, - "notionalFloor": 40000000, - "notionalCap": 400000000, + "minNotional": 40000000, + "maxNotional": 400000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "400000000", - "notionalFloor": "40000000", + "maxNotional": "400000000", + "minNotional": "40000000", "maintMarginRatio": "0.5", "cum": "1.246125E7" } @@ -14140,105 +14140,105 @@ "FTM/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 750000, + "minNotional": 500000, + "maxNotional": 750000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "750000", - "notionalFloor": "500000", + "maxNotional": "750000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 750000, - "notionalCap": 1000000, + "minNotional": 750000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "750000", + "maxNotional": "1000000", + "minNotional": "750000", "maintMarginRatio": "0.25", "cum": "123250.0" } }, { "tier": 7, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "373250.0" } @@ -14247,90 +14247,90 @@ "YFII/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -14339,90 +14339,90 @@ "KEEP/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -14431,90 +14431,90 @@ "BAND/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -14523,135 +14523,135 @@ "BTCBUSD_210226": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.004, "maxLeverage": 20, "info": { "bracket": "1", "initialLeverage": "20", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.004", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.005, "maxLeverage": 15, "info": { "bracket": "2", "initialLeverage": "15", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.005", "cum": "5.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.01, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.01", "cum": "130.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 500000, + "minNotional": 100000, + "maxNotional": 500000, "maintenanceMarginRate": 0.025, "maxLeverage": 7, "info": { "bracket": "4", "initialLeverage": "7", - "notionalCap": "500000", - "notionalFloor": "100000", + "maxNotional": "500000", + "minNotional": "100000", "maintMarginRatio": "0.025", "cum": "1630.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 2000000, + "minNotional": 500000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.05, "maxLeverage": 6, "info": { "bracket": "5", "initialLeverage": "6", - "notionalCap": "2000000", - "notionalFloor": "500000", + "maxNotional": "2000000", + "minNotional": "500000", "maintMarginRatio": "0.05", "cum": "14130.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "6", "initialLeverage": "5", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.1", "cum": "114130.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "7", "initialLeverage": "4", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.125", "cum": "239130.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "8", "initialLeverage": "3", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.15", "cum": "489130.0" } }, { "tier": 9, - "notionalFloor": 20000000, - "notionalCap": 9223372036854776000, + "minNotional": 20000000, + "maxNotional": 9223372036854776000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "9", "initialLeverage": "2", - "notionalCap": "9223372036854775807", - "notionalFloor": "20000000", + "maxNotional": "9223372036854775807", + "minNotional": "20000000", "maintMarginRatio": "0.25", "cum": "2489130.0" } @@ -14660,90 +14660,90 @@ "XRP/BUSD": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 100000, + "minNotional": 0, + "maxNotional": 100000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "1", "initialLeverage": "20", - "notionalCap": "100000", - "notionalFloor": "0", + "maxNotional": "100000", + "minNotional": "0", "maintMarginRatio": "0.025", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 100000, - "notionalCap": 500000, + "minNotional": 100000, + "maxNotional": 500000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "500000", - "notionalFloor": "100000", + "maxNotional": "500000", + "minNotional": "100000", "maintMarginRatio": "0.05", "cum": "2500.0" } }, { "tier": 3, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.1", "cum": "27500.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "4", "initialLeverage": "3", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.15", "cum": "77500.0" } }, { "tier": 5, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.25", "cum": "277500.0" } }, { "tier": 6, - "notionalFloor": 5000000, - "notionalCap": 30000000, + "minNotional": 5000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "5000000", + "maxNotional": "30000000", + "minNotional": "5000000", "maintMarginRatio": "0.5", "cum": "1527500.0" } @@ -14752,90 +14752,90 @@ "DOGE/BUSD": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 100000, + "minNotional": 0, + "maxNotional": 100000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "1", "initialLeverage": "20", - "notionalCap": "100000", - "notionalFloor": "0", + "maxNotional": "100000", + "minNotional": "0", "maintMarginRatio": "0.025", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 100000, - "notionalCap": 500000, + "minNotional": 100000, + "maxNotional": 500000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "500000", - "notionalFloor": "100000", + "maxNotional": "500000", + "minNotional": "100000", "maintMarginRatio": "0.05", "cum": "2500.0" } }, { "tier": 3, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.1", "cum": "27500.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "4", "initialLeverage": "3", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.15", "cum": "77500.0" } }, { "tier": 5, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.25", "cum": "277500.0" } }, { "tier": 6, - "notionalFloor": 5000000, - "notionalCap": 30000000, + "minNotional": 5000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "5000000", + "maxNotional": "30000000", + "minNotional": "5000000", "maintMarginRatio": "0.5", "cum": "1527500.0" } @@ -14844,135 +14844,135 @@ "XRP/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 50000, + "minNotional": 10000, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "10000", + "maxNotional": "50000", + "minNotional": "10000", "maintMarginRatio": "0.01", "cum": "35.0" } }, { "tier": 3, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "535.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8035.0" } }, { "tier": 5, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58035.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108035.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "233035.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 20000000, + "minNotional": 10000000, + "maxNotional": 20000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "20000000", - "notionalFloor": "10000000", + "maxNotional": "20000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1233035.0" } }, { "tier": 9, - "notionalFloor": 20000000, - "notionalCap": 50000000, + "minNotional": 20000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "20000000", + "maxNotional": "50000000", + "minNotional": "20000000", "maintMarginRatio": "0.5", "cum": "6233035.0" } @@ -14981,90 +14981,90 @@ "SXP/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -15073,105 +15073,105 @@ "CRV/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -15180,90 +15180,90 @@ "BEL/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -15272,135 +15272,135 @@ "DOT/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 10000, + "minNotional": 0, + "maxNotional": 10000, "maintenanceMarginRate": 0.0065, "maxLeverage": 75, "info": { "bracket": "1", "initialLeverage": "75", - "notionalCap": "10000", - "notionalFloor": "0", + "maxNotional": "10000", + "minNotional": "0", "maintMarginRatio": "0.0065", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 10000, - "notionalCap": 50000, + "minNotional": 10000, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "2", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "10000", + "maxNotional": "50000", + "minNotional": "10000", "maintMarginRatio": "0.01", "cum": "35.0" } }, { "tier": 3, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "3", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "535.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "4", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8035.0" } }, { "tier": 5, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "5", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58035.0" } }, { "tier": 6, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "6", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108035.0" } }, { "tier": 7, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "7", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.15", "cum": "233035.0" } }, { "tier": 8, - "notionalFloor": 10000000, - "notionalCap": 50000000, + "minNotional": 10000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "8", "initialLeverage": "2", - "notionalCap": "50000000", - "notionalFloor": "10000000", + "maxNotional": "50000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1233035.0" } }, { "tier": 9, - "notionalFloor": 50000000, - "notionalCap": 100000000, + "minNotional": 50000000, + "maxNotional": 100000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "9", "initialLeverage": "1", - "notionalCap": "100000000", - "notionalFloor": "50000000", + "maxNotional": "100000000", + "minNotional": "50000000", "maintMarginRatio": "0.5", "cum": "1.3733035E7" } @@ -15409,90 +15409,90 @@ "XEM/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -15501,105 +15501,105 @@ "ONE/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } @@ -15608,90 +15608,90 @@ "ZIL/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -15700,120 +15700,120 @@ "AXS/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 250000, + "minNotional": 50000, + "maxNotional": 250000, "maintenanceMarginRate": 0.02, "maxLeverage": 25, "info": { "bracket": "2", "initialLeverage": "25", - "notionalCap": "250000", - "notionalFloor": "50000", + "maxNotional": "250000", + "minNotional": "50000", "maintMarginRatio": "0.02", "cum": "500.0" } }, { "tier": 3, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.05", "cum": "8000.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.1", "cum": "58000.0" } }, { "tier": 5, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.125", "cum": "108000.0" } }, { "tier": 6, - "notionalFloor": 5000000, - "notionalCap": 10000000, + "minNotional": 5000000, + "maxNotional": 10000000, "maintenanceMarginRate": 0.1665, "maxLeverage": 3, "info": { "bracket": "6", "initialLeverage": "3", - "notionalCap": "10000000", - "notionalFloor": "5000000", + "maxNotional": "10000000", + "minNotional": "5000000", "maintMarginRatio": "0.1665", "cum": "315500.0" } }, { "tier": 7, - "notionalFloor": 10000000, - "notionalCap": 15000000, + "minNotional": 10000000, + "maxNotional": 15000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "7", "initialLeverage": "2", - "notionalCap": "15000000", - "notionalFloor": "10000000", + "maxNotional": "15000000", + "minNotional": "10000000", "maintMarginRatio": "0.25", "cum": "1150500.0" } }, { "tier": 8, - "notionalFloor": 15000000, - "notionalCap": 50000000, + "minNotional": 15000000, + "maxNotional": 50000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "8", "initialLeverage": "1", - "notionalCap": "50000000", - "notionalFloor": "15000000", + "maxNotional": "50000000", + "minNotional": "15000000", "maintMarginRatio": "0.5", "cum": "4900500.0" } @@ -15822,105 +15822,105 @@ "DYDX/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 4000000, + "minNotional": 1000000, + "maxNotional": 4000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "4000000", - "notionalFloor": "1000000", + "maxNotional": "4000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 4000000, - "notionalCap": 30000000, + "minNotional": 4000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "4000000", + "maxNotional": "30000000", + "minNotional": "4000000", "maintMarginRatio": "0.5", "cum": "1154500.0" } @@ -15929,90 +15929,90 @@ "OCEAN/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -16021,90 +16021,90 @@ "CHZ/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.012, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.012", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "65.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "690.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5690.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11940.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386940.0" } @@ -16113,90 +16113,90 @@ "LENDUSDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 9223372036854776000, + "minNotional": 1000000, + "maxNotional": 9223372036854776000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "9223372036854775807", - "notionalFloor": "1000000", + "maxNotional": "9223372036854775807", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -16205,90 +16205,90 @@ "ANKR/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.012, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.012", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "65.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "690.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5690.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11940.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386940.0" } @@ -16297,90 +16297,90 @@ "DUSK/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } @@ -16389,90 +16389,90 @@ "CTSI/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 5000, + "minNotional": 0, + "maxNotional": 5000, "maintenanceMarginRate": 0.01, "maxLeverage": 25, "info": { "bracket": "1", "initialLeverage": "25", - "notionalCap": "5000", - "notionalFloor": "0", + "maxNotional": "5000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 5000, - "notionalCap": 25000, + "minNotional": 5000, + "maxNotional": 25000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "25000", - "notionalFloor": "5000", + "maxNotional": "25000", + "minNotional": "5000", "maintMarginRatio": "0.025", "cum": "75.0" } }, { "tier": 3, - "notionalFloor": 25000, - "notionalCap": 100000, + "minNotional": 25000, + "maxNotional": 100000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "100000", - "notionalFloor": "25000", + "maxNotional": "100000", + "minNotional": "25000", "maintMarginRatio": "0.05", "cum": "700.0" } }, { "tier": 4, - "notionalFloor": 100000, - "notionalCap": 250000, + "minNotional": 100000, + "maxNotional": 250000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "250000", - "notionalFloor": "100000", + "maxNotional": "250000", + "minNotional": "100000", "maintMarginRatio": "0.1", "cum": "5700.0" } }, { "tier": 5, - "notionalFloor": 250000, - "notionalCap": 1000000, + "minNotional": 250000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "1000000", - "notionalFloor": "250000", + "maxNotional": "1000000", + "minNotional": "250000", "maintMarginRatio": "0.125", "cum": "11950.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 30000000, + "minNotional": 1000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "1000000", + "maxNotional": "30000000", + "minNotional": "1000000", "maintMarginRatio": "0.5", "cum": "386950.0" } diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index e0da0878d..ab063762b 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2137,8 +2137,8 @@ class Exchange: def parse_leverage_tier(self, tier) -> Dict: info = tier.get('info', {}) return { - 'min': tier['notionalFloor'], - 'max': tier['notionalCap'], + 'min': tier['minNotional'], + 'max': tier['maxNotional'], 'mmr': tier['maintenanceMarginRate'], 'lev': tier['maxLeverage'], 'maintAmt': float(info['cum']) if 'cum' in info else None, diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index c3950e459..5c8d7d3b0 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -169,90 +169,90 @@ def test_fill_leverage_tiers_binance(default_conf, mocker): 'ADA/BUSD': [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 100000, + "minNotional": 0, + "maxNotional": 100000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "1", "initialLeverage": "20", - "notionalCap": "100000", - "notionalFloor": "0", + "maxNotional": "100000", + "minNotional": "0", "maintMarginRatio": "0.025", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 100000, - "notionalCap": 500000, + "minNotional": 100000, + "maxNotional": 500000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "2", "initialLeverage": "10", - "notionalCap": "500000", - "notionalFloor": "100000", + "maxNotional": "500000", + "minNotional": "100000", "maintMarginRatio": "0.05", "cum": "2500.0" } }, { "tier": 3, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "3", "initialLeverage": "5", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.1", "cum": "27500.0" } }, { "tier": 4, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.15, "maxLeverage": 3, "info": { "bracket": "4", "initialLeverage": "3", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.15", "cum": "77500.0" } }, { "tier": 5, - "notionalFloor": 2000000, - "notionalCap": 5000000, + "minNotional": 2000000, + "maxNotional": 5000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "5", "initialLeverage": "2", - "notionalCap": "5000000", - "notionalFloor": "2000000", + "maxNotional": "5000000", + "minNotional": "2000000", "maintMarginRatio": "0.25", "cum": "277500.0" } }, { "tier": 6, - "notionalFloor": 5000000, - "notionalCap": 30000000, + "minNotional": 5000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "6", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "5000000", + "maxNotional": "30000000", + "minNotional": "5000000", "maintMarginRatio": "0.5", "cum": "1527500.0" } @@ -261,105 +261,105 @@ def test_fill_leverage_tiers_binance(default_conf, mocker): "ZEC/USDT": [ { "tier": 1, - "notionalFloor": 0, - "notionalCap": 50000, + "minNotional": 0, + "maxNotional": 50000, "maintenanceMarginRate": 0.01, "maxLeverage": 50, "info": { "bracket": "1", "initialLeverage": "50", - "notionalCap": "50000", - "notionalFloor": "0", + "maxNotional": "50000", + "minNotional": "0", "maintMarginRatio": "0.01", "cum": "0.0" } }, { "tier": 2, - "notionalFloor": 50000, - "notionalCap": 150000, + "minNotional": 50000, + "maxNotional": 150000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "2", "initialLeverage": "20", - "notionalCap": "150000", - "notionalFloor": "50000", + "maxNotional": "150000", + "minNotional": "50000", "maintMarginRatio": "0.025", "cum": "750.0" } }, { "tier": 3, - "notionalFloor": 150000, - "notionalCap": 250000, + "minNotional": 150000, + "maxNotional": 250000, "maintenanceMarginRate": 0.05, "maxLeverage": 10, "info": { "bracket": "3", "initialLeverage": "10", - "notionalCap": "250000", - "notionalFloor": "150000", + "maxNotional": "250000", + "minNotional": "150000", "maintMarginRatio": "0.05", "cum": "4500.0" } }, { "tier": 4, - "notionalFloor": 250000, - "notionalCap": 500000, + "minNotional": 250000, + "maxNotional": 500000, "maintenanceMarginRate": 0.1, "maxLeverage": 5, "info": { "bracket": "4", "initialLeverage": "5", - "notionalCap": "500000", - "notionalFloor": "250000", + "maxNotional": "500000", + "minNotional": "250000", "maintMarginRatio": "0.1", "cum": "17000.0" } }, { "tier": 5, - "notionalFloor": 500000, - "notionalCap": 1000000, + "minNotional": 500000, + "maxNotional": 1000000, "maintenanceMarginRate": 0.125, "maxLeverage": 4, "info": { "bracket": "5", "initialLeverage": "4", - "notionalCap": "1000000", - "notionalFloor": "500000", + "maxNotional": "1000000", + "minNotional": "500000", "maintMarginRatio": "0.125", "cum": "29500.0" } }, { "tier": 6, - "notionalFloor": 1000000, - "notionalCap": 2000000, + "minNotional": 1000000, + "maxNotional": 2000000, "maintenanceMarginRate": 0.25, "maxLeverage": 2, "info": { "bracket": "6", "initialLeverage": "2", - "notionalCap": "2000000", - "notionalFloor": "1000000", + "maxNotional": "2000000", + "minNotional": "1000000", "maintMarginRatio": "0.25", "cum": "154500.0" } }, { "tier": 7, - "notionalFloor": 2000000, - "notionalCap": 30000000, + "minNotional": 2000000, + "maxNotional": 30000000, "maintenanceMarginRate": 0.5, "maxLeverage": 1, "info": { "bracket": "7", "initialLeverage": "1", - "notionalCap": "30000000", - "notionalFloor": "2000000", + "maxNotional": "30000000", + "minNotional": "2000000", "maintMarginRatio": "0.5", "cum": "654500.0" } diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index 14e45c8b0..2a148c388 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -369,25 +369,25 @@ class TestCCXTExchange(): pair_tiers = leverage_tiers[futures_pair] assert len(pair_tiers) > 0 oldLeverage = float('inf') - oldMaintenanceMarginRate = oldNotionalFloor = oldNotionalCap = -1 + oldMaintenanceMarginRate = oldminNotional = oldmaxNotional = -1 for tier in pair_tiers: for key in [ 'maintenanceMarginRate', - 'notionalFloor', - 'notionalCap', + 'minNotional', + 'maxNotional', 'maxLeverage' ]: assert key in tier assert tier[key] >= 0.0 - assert tier['notionalCap'] > tier['notionalFloor'] + assert tier['maxNotional'] > tier['minNotional'] assert tier['maxLeverage'] <= oldLeverage assert tier['maintenanceMarginRate'] >= oldMaintenanceMarginRate - assert tier['notionalFloor'] > oldNotionalFloor - assert tier['notionalCap'] > oldNotionalCap + assert tier['minNotional'] > oldminNotional + assert tier['maxNotional'] > oldmaxNotional oldLeverage = tier['maxLeverage'] oldMaintenanceMarginRate = tier['maintenanceMarginRate'] - oldNotionalFloor = tier['notionalFloor'] - oldNotionalCap = tier['notionalCap'] + oldminNotional = tier['minNotional'] + oldmaxNotional = tier['maxNotional'] def test_ccxt_dry_run_liquidation_price(self, exchange_futures): futures, futures_name = exchange_futures diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index e99121489..a6918b6d4 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -4507,8 +4507,8 @@ def test_load_leverage_tiers(mocker, default_conf, leverage_tiers, exchange_name 'ADA/USDT:USDT': [ { 'tier': 1, - 'notionalFloor': 0, - 'notionalCap': 500, + 'minNotional': 0, + 'maxNotional': 500, 'maintenanceMarginRate': 0.02, 'maxLeverage': 75, 'info': { @@ -4548,8 +4548,8 @@ def test_load_leverage_tiers(mocker, default_conf, leverage_tiers, exchange_name 'ADA/USDT:USDT': [ { 'tier': 1, - 'notionalFloor': 0, - 'notionalCap': 500, + 'minNotional': 0, + 'maxNotional': 500, 'maintenanceMarginRate': 0.02, 'maxLeverage': 75, 'info': { @@ -4584,15 +4584,15 @@ def test_parse_leverage_tier(mocker, default_conf): tier = { "tier": 1, - "notionalFloor": 0, - "notionalCap": 100000, + "minNotional": 0, + "maxNotional": 100000, "maintenanceMarginRate": 0.025, "maxLeverage": 20, "info": { "bracket": "1", "initialLeverage": "20", - "notionalCap": "100000", - "notionalFloor": "0", + "maxNotional": "100000", + "minNotional": "0", "maintMarginRatio": "0.025", "cum": "0.0" } @@ -4608,8 +4608,8 @@ def test_parse_leverage_tier(mocker, default_conf): tier2 = { 'tier': 1, - 'notionalFloor': 0, - 'notionalCap': 2000, + 'minNotional': 0, + 'maxNotional': 2000, 'maintenanceMarginRate': 0.01, 'maxLeverage': 75, 'info': { diff --git a/tests/exchange/test_okx.py b/tests/exchange/test_okx.py index 8ecdf6904..37c1ea974 100644 --- a/tests/exchange/test_okx.py +++ b/tests/exchange/test_okx.py @@ -19,8 +19,8 @@ def test_get_maintenance_ratio_and_amt_okx( 'ETH/USDT:USDT': [ { 'tier': 1, - 'notionalFloor': 0, - 'notionalCap': 2000, + 'minNotional': 0, + 'maxNotional': 2000, 'maintenanceMarginRate': 0.01, 'maxLeverage': 75, 'info': { @@ -39,8 +39,8 @@ def test_get_maintenance_ratio_and_amt_okx( }, { 'tier': 2, - 'notionalFloor': 2001, - 'notionalCap': 4000, + 'minNotional': 2001, + 'maxNotional': 4000, 'maintenanceMarginRate': 0.015, 'maxLeverage': 50, 'info': { @@ -59,8 +59,8 @@ def test_get_maintenance_ratio_and_amt_okx( }, { 'tier': 3, - 'notionalFloor': 4001, - 'notionalCap': 8000, + 'minNotional': 4001, + 'maxNotional': 8000, 'maintenanceMarginRate': 0.02, 'maxLeverage': 20, 'info': { @@ -81,8 +81,8 @@ def test_get_maintenance_ratio_and_amt_okx( 'ADA/USDT:USDT': [ { 'tier': 1, - 'notionalFloor': 0, - 'notionalCap': 500, + 'minNotional': 0, + 'maxNotional': 500, 'maintenanceMarginRate': 0.02, 'maxLeverage': 75, 'info': { @@ -101,8 +101,8 @@ def test_get_maintenance_ratio_and_amt_okx( }, { 'tier': 2, - 'notionalFloor': 501, - 'notionalCap': 1000, + 'minNotional': 501, + 'maxNotional': 1000, 'maintenanceMarginRate': 0.025, 'maxLeverage': 50, 'info': { @@ -121,8 +121,8 @@ def test_get_maintenance_ratio_and_amt_okx( }, { 'tier': 3, - 'notionalFloor': 1001, - 'notionalCap': 2000, + 'minNotional': 1001, + 'maxNotional': 2000, 'maintenanceMarginRate': 0.03, 'maxLeverage': 20, 'info': { @@ -180,8 +180,8 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets): [ { 'tier': 1, - 'notionalFloor': 0, - 'notionalCap': 500, + 'minNotional': 0, + 'maxNotional': 500, 'maintenanceMarginRate': 0.02, 'maxLeverage': 75, 'info': { @@ -200,8 +200,8 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets): }, { 'tier': 2, - 'notionalFloor': 501, - 'notionalCap': 1000, + 'minNotional': 501, + 'maxNotional': 1000, 'maintenanceMarginRate': 0.025, 'maxLeverage': 50, 'info': { @@ -220,8 +220,8 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets): }, { 'tier': 3, - 'notionalFloor': 1001, - 'notionalCap': 2000, + 'minNotional': 1001, + 'maxNotional': 2000, 'maintenanceMarginRate': 0.03, 'maxLeverage': 20, 'info': { @@ -242,8 +242,8 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets): [ { 'tier': 1, - 'notionalFloor': 0, - 'notionalCap': 2000, + 'minNotional': 0, + 'maxNotional': 2000, 'maintenanceMarginRate': 0.01, 'maxLeverage': 75, 'info': { @@ -262,8 +262,8 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets): }, { 'tier': 2, - 'notionalFloor': 2001, - 'notionalCap': 4000, + 'minNotional': 2001, + 'maxNotional': 4000, 'maintenanceMarginRate': 0.015, 'maxLeverage': 50, 'info': { @@ -282,8 +282,8 @@ def test_load_leverage_tiers_okx(default_conf, mocker, markets): }, { 'tier': 3, - 'notionalFloor': 4001, - 'notionalCap': 8000, + 'minNotional': 4001, + 'maxNotional': 8000, 'maintenanceMarginRate': 0.02, 'maxLeverage': 20, 'info': { From 80b34deb4dd19eaebcc8e11fa22b815057de510d Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 17 Apr 2022 14:38:34 -0600 Subject: [PATCH 108/254] bump ccxt to 1.79.69 to account for https://github.com/ccxt/ccxt/pull/12873" --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3f2d1b351..07bb04be3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.22.3 pandas==1.4.2 pandas-ta==0.3.14b -ccxt==1.78.62 +ccxt==1.79.69 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.2 aiohttp==3.8.1 From a1e425a8013e881229bf8d61c7f2b5bcb686138e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Apr 2022 03:01:19 +0000 Subject: [PATCH 109/254] Bump fastapi from 0.75.1 to 0.75.2 Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.75.1 to 0.75.2. - [Release notes](https://github.com/tiangolo/fastapi/releases) - [Commits](https://github.com/tiangolo/fastapi/compare/0.75.1...0.75.2) --- updated-dependencies: - dependency-name: fastapi dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3f2d1b351..2d23dd1ae 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,7 +31,7 @@ python-rapidjson==1.6 sdnotify==0.3.2 # API Server -fastapi==0.75.1 +fastapi==0.75.2 uvicorn==0.17.6 pyjwt==2.3.0 aiofiles==0.8.0 From 2ff18e78c40b13ae30ca47d8f2f29ce4c58e7767 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Apr 2022 03:01:22 +0000 Subject: [PATCH 110/254] Bump types-cachetools from 5.0.0 to 5.0.1 Bumps [types-cachetools](https://github.com/python/typeshed) from 5.0.0 to 5.0.1. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-cachetools dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 03bb5e72d..764efd6ed 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -22,7 +22,7 @@ time-machine==2.6.0 nbconvert==6.4.5 # mypy types -types-cachetools==5.0.0 +types-cachetools==5.0.1 types-filelock==3.2.5 types-requests==2.27.16 types-tabulate==0.8.6 From 7d88dcb5f46d56296ae1cc8b2b0b4d4f52e3e0f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Apr 2022 03:01:24 +0000 Subject: [PATCH 111/254] Bump types-tabulate from 0.8.6 to 0.8.7 Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.6 to 0.8.7. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-tabulate dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 03bb5e72d..489879978 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -25,7 +25,7 @@ nbconvert==6.4.5 types-cachetools==5.0.0 types-filelock==3.2.5 types-requests==2.27.16 -types-tabulate==0.8.6 +types-tabulate==0.8.7 # Extensions to datetime library types-python-dateutil==2.8.10 From f020d129d7ddf322398fded766f0fa967c1cf6cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Apr 2022 03:01:27 +0000 Subject: [PATCH 112/254] Bump types-python-dateutil from 2.8.10 to 2.8.11 Bumps [types-python-dateutil](https://github.com/python/typeshed) from 2.8.10 to 2.8.11. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-python-dateutil dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 03bb5e72d..416d62e0d 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -28,4 +28,4 @@ types-requests==2.27.16 types-tabulate==0.8.6 # Extensions to datetime library -types-python-dateutil==2.8.10 +types-python-dateutil==2.8.11 From b69483e3afdd5438ced33aff2aec5baa7350a03d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Apr 2022 03:01:42 +0000 Subject: [PATCH 113/254] Bump nbconvert from 6.4.5 to 6.5.0 Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 6.4.5 to 6.5.0. - [Release notes](https://github.com/jupyter/nbconvert/releases) - [Commits](https://github.com/jupyter/nbconvert/compare/6.4.5...6.5) --- updated-dependencies: - dependency-name: nbconvert dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 03bb5e72d..43e9b01dd 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -19,7 +19,7 @@ isort==5.10.1 time-machine==2.6.0 # Convert jupyter notebooks to markdown documents -nbconvert==6.4.5 +nbconvert==6.5.0 # mypy types types-cachetools==5.0.0 From 153b31c9348ffa072126dfb4094962daf3317bfd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Apr 2022 06:11:57 +0000 Subject: [PATCH 114/254] Bump types-requests from 2.27.16 to 2.27.19 Bumps [types-requests](https://github.com/python/typeshed) from 2.27.16 to 2.27.19. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 70f5aaa3e..4fb4456f0 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -24,7 +24,7 @@ nbconvert==6.5.0 # mypy types types-cachetools==5.0.1 types-filelock==3.2.5 -types-requests==2.27.16 +types-requests==2.27.19 types-tabulate==0.8.7 # Extensions to datetime library From 1ce20ea739029a4825a4d7a4f5d0aad6c1a37dad Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 18 Apr 2022 08:16:38 +0200 Subject: [PATCH 115/254] Bump requirements to 1.79.81, min-required to 1.79.69 --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 07bb04be3..b225d7ff2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.22.3 pandas==1.4.2 pandas-ta==0.3.14b -ccxt==1.79.69 +ccxt==1.79.81 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.2 aiohttp==3.8.1 diff --git a/setup.py b/setup.py index 640c8cc7b..250cafdc9 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ setup( ], install_requires=[ # from requirements.txt - 'ccxt>=1.77.29', + 'ccxt>=1.79.69', 'SQLAlchemy', 'python-telegram-bot>=13.4', 'arrow>=0.17.0', From 84f486295d806d4ae8347582f7d3d803bf41d256 Mon Sep 17 00:00:00 2001 From: froggleston Date: Tue, 19 Apr 2022 12:48:21 +0100 Subject: [PATCH 116/254] Add tests for new storing of backtest signal candles --- freqtrade/misc.py | 16 ++++++++++++++++ freqtrade/optimize/optimize_reports.py | 10 ++++------ tests/optimize/test_optimize_reports.py | 24 +++++++++++++++++++++++- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index d5572ea0b..9087ec6e2 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -4,6 +4,7 @@ Various tool function for Freqtrade and scripts import gzip import hashlib import logging +import pickle import re from copy import deepcopy from datetime import datetime @@ -86,6 +87,21 @@ def file_dump_json(filename: Path, data: Any, is_zip: bool = False, log: bool = logger.debug(f'done json to "{filename}"') +def file_dump_pickle(filename: Path, data: Any, log: bool = True) -> None: + """ + Dump object data into a file + :param filename: file to create + :param data: Object data to save + :return: + """ + + if log: + logger.info(f'dumping pickle to "{filename}"') + with open(filename, 'wb') as fp: + pickle.dump(data, fp) + logger.debug(f'done pickling to "{filename}"') + + def json_load(datafile: IO) -> Any: """ load data with rapidjson diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index f0b2e2e71..ed29af839 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -1,5 +1,4 @@ import logging -import pickle from copy import deepcopy from datetime import datetime, timedelta, timezone from pathlib import Path @@ -12,8 +11,8 @@ from tabulate import tabulate from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN, UNLIMITED_STAKE_AMOUNT from freqtrade.data.btanalysis import (calculate_csum, calculate_market_change, calculate_max_drawdown) -from freqtrade.misc import (decimals_per_coin, file_dump_json, get_backtest_metadata_filename, - round_coin_value) +from freqtrade.misc import (decimals_per_coin, file_dump_json, file_dump_pickle, + get_backtest_metadata_filename, round_coin_value) logger = logging.getLogger(__name__) @@ -61,11 +60,10 @@ def store_backtest_signal_candles(recordfilename: Path, candles: Dict[str, Dict] else: filename = Path.joinpath( recordfilename.parent, - f'{recordfilename.stem}-{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}' + f'{recordfilename.stem}-{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}_signals' ).with_suffix(recordfilename.suffix) - with open(filename, 'wb') as f: - pickle.dump(candles, f) + file_dump_pickle(filename, candles) def _get_line_floatfmt(stake_currency: str) -> List[str]: diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 05c0bf575..a09620a71 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -1,5 +1,5 @@ import re -from datetime import timedelta +from datetime import timedelta, timezone from pathlib import Path import pandas as pd @@ -19,6 +19,7 @@ from freqtrade.optimize.optimize_reports import (_get_resample_from_period, gene generate_periodic_breakdown_stats, generate_strategy_comparison, generate_trading_stats, show_sorted_pairlist, + store_backtest_signal_candles, store_backtest_stats, text_table_bt_results, text_table_exit_reason, text_table_strategy) from freqtrade.resolvers.strategy_resolver import StrategyResolver @@ -201,6 +202,27 @@ def test_store_backtest_stats(testdatadir, mocker): assert str(dump_mock.call_args_list[0][0][0]).startswith(str(testdatadir / 'testresult')) +def test_store_backtest_candles(testdatadir, mocker): + + dump_mock = mocker.patch('freqtrade.optimize.optimize_reports.file_dump_pickle') + + # test directory exporting + store_backtest_signal_candles(testdatadir, {'DefStrat': {'UNITTEST/BTC': pd.DataFrame()}}) + + assert dump_mock.call_count == 1 + assert isinstance(dump_mock.call_args_list[0][0][0], Path) + assert str(dump_mock.call_args_list[0][0][0]).endswith(str('_signals.pkl')) + + dump_mock.reset_mock() + # test file exporting + filename = testdatadir / 'testresult' + store_backtest_signal_candles(filename, {'DefStrat': {'UNITTEST/BTC': pd.DataFrame()}}) + assert dump_mock.call_count == 1 + assert isinstance(dump_mock.call_args_list[0][0][0], Path) + # result will be testdatadir / testresult-_signals.pkl + assert str(dump_mock.call_args_list[0][0][0]).endswith(str('_signals.pkl')) + + def test_generate_pair_metrics(): results = pd.DataFrame( From aa5984930d3cb19b7fc2ed82b5590a6bad640d42 Mon Sep 17 00:00:00 2001 From: froggleston Date: Tue, 19 Apr 2022 13:00:09 +0100 Subject: [PATCH 117/254] Fix filename generation --- freqtrade/optimize/optimize_reports.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index ed29af839..05eec693e 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -60,8 +60,8 @@ def store_backtest_signal_candles(recordfilename: Path, candles: Dict[str, Dict] else: filename = Path.joinpath( recordfilename.parent, - f'{recordfilename.stem}-{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}_signals' - ).with_suffix(recordfilename.suffix) + f'{recordfilename.stem}-{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}_signals.pkl' + ) file_dump_pickle(filename, candles) From 3ad1411f5ecba585be2a23972d412f692da15018 Mon Sep 17 00:00:00 2001 From: froggleston Date: Tue, 19 Apr 2022 13:08:01 +0100 Subject: [PATCH 118/254] Fix imports --- tests/optimize/test_optimize_reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index a09620a71..d72cf4e86 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -1,5 +1,5 @@ import re -from datetime import timedelta, timezone +from datetime import timedelta from pathlib import Path import pandas as pd From 9421d19cba64813a16094ff3bf2d332641e3fd89 Mon Sep 17 00:00:00 2001 From: froggleston Date: Tue, 19 Apr 2022 14:05:03 +0100 Subject: [PATCH 119/254] Add documentation --- docs/configuration.md | 7 +-- docs/strategy_analysis_example.md | 74 ++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 369c4e2dd..061b6c77c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -11,7 +11,7 @@ Per default, the bot loads the configuration from the `config.json` file, locate You can specify a different configuration file used by the bot with the `-c/--config` command-line option. -If you used the [Quick start](installation.md/#quick-start) method for installing +If you used the [Quick start](installation.md/#quick-start) method for installing the bot, the installation script should have already created the default configuration file (`config.json`) for you. If the default configuration file is not created we recommend to use `freqtrade new-config --config config.json` to generate a basic configuration file. @@ -64,7 +64,7 @@ This is similar to using multiple `--config` parameters, but simpler in usage as "config-private.json" ] ``` - + ``` bash freqtrade trade --config user_data/config.json <...> ``` @@ -100,7 +100,7 @@ This is similar to using multiple `--config` parameters, but simpler in usage as "stake_amount": "unlimited", } ``` - + Resulting combined configuration: ``` json title="Result" @@ -229,6 +229,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `dataformat_trades` | Data format to use to store historical trades data.
*Defaults to `jsongz`*.
**Datatype:** String | `position_adjustment_enable` | Enables the strategy to use position adjustments (additional buys or sells). [More information here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean | `max_entry_position_adjustment` | Maximum additional order(s) for each open trade on top of the first entry Order. Set it to `-1` for unlimited additional orders. [More information here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `-1`.*
**Datatype:** Positive Integer or -1 +| `backtest_signal_candle_export_enable` | Enables the exporting of signal candles for use in post-backtesting analysis of buy tags. See [Strategy Analysis](strategy_analysis_example.md#analyse-the-buy-entry-and-sell-exit-tags).
*Defaults to `false`.*
**Datatype:** Boolean ### Parameters in the strategy diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index 2fa84a6df..48f54c824 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -93,7 +93,7 @@ from freqtrade.data.btanalysis import load_backtest_data, load_backtest_stats # if backtest_dir points to a directory, it'll automatically load the last backtest file. backtest_dir = config["user_data_dir"] / "backtest_results" -# backtest_dir can also point to a specific file +# backtest_dir can also point to a specific file # backtest_dir = config["user_data_dir"] / "backtest_results/backtest-result-2020-07-01_20-04-22.json" ``` @@ -250,3 +250,75 @@ fig.show() ``` Feel free to submit an issue or Pull Request enhancing this document if you would like to share ideas on how to best analyze the data. + +## Analyse the buy/entry and sell/exit tags + +It can be helpful to understand how a strategy behaves according to the buy/entry tags used to +mark up different buy conditions. You might want to see more complex statistics about each buy and +sell condition above those provided by the default backtesting output. You may also want to +determine indicator values on the signal candle that resulted in a trade opening. + +We first need to enable the exporting of trades from backtesting: + +``` +freqtrade backtesting -c --timeframe --strategy --timerange= --export=trades --export-filename=user_data/backtest_results/- +``` + +To analyse the buy tags, we need to use the buy_reasons.py script in the `scripts/` +folder. We need the signal candles for each opened trade so add the following option to your +config file: + +``` +'backtest_signal_candle_export_enable': true, +``` + +This will tell freqtrade to output a pickled dictionary of strategy, pairs and corresponding +DataFrame of the candles that resulted in buy signals. Depending on how many buys your strategy +makes, this file may get quite large, so periodically check your `user_data/backtest_results` +folder to delete old exports. + +Before running your next backtest, make sure you either delete your old backtest results or run +backtesting with the `--cache none` option to make sure no cached results are used. + +If all goes well, you should now see a `backtest-result-{timestamp}_signals.pkl` file in the +`user_data/backtest_results` folder. + +Now run the buy_reasons.py script, supplying a few options: + +``` +./scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 +``` + +The `-g` option is used to specify the various tabular outputs, ranging from the simplest (0) +to the most detailed per pair, per buy and per sell tag (4). More options are available by +running with the `-h` option. + +### Tuning the buy tags and sell tags to display + +To show only certain buy and sell tags in the displayed output, use the following two options: + +``` +--buy_reason_list : Comma separated list of buy signals to analyse. Default: "all" +--sell_reason_list : Comma separated list of sell signals to analyse. Default: "stop_loss,trailing_stop_loss" +``` + +For example: + +``` +./scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 --buy_reason_list "buy_tag_a,buy_tag_b" --sell_reason_list "roi,custom_sell_tag_a,stop_loss" +``` + +### Outputting signal candle indicators + +The real power of the buy_reasons.py script comes from the ability to print out the indicator +values present on signal candles to allow fine-grained investigation and tuning of buy signal +indicators. To print out a column for a given set of indicators, use the `--indicator-list` +option: + +``` +./scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 --buy_reason_list "buy_tag_a,buy_tag_b" --sell_reason_list "roi,custom_sell_tag_a,stop_loss" --indicator_list "rsi,rsi_1h,bb_lowerband,ema_9,macd,macdsignal" +``` + +The indicators have to be present in your strategy's main dataframe (either for your main +timeframe or for informatives) otherwise they will simply be ignored in the script +output. From b3cb7226467c359b0748cb4866f8f8fcfa649a53 Mon Sep 17 00:00:00 2001 From: froggleston Date: Wed, 20 Apr 2022 13:38:52 +0100 Subject: [PATCH 120/254] Use joblib instead of pickle, add signal candle read/write test, move docs to new Advanced Backtesting doc --- docs/advanced-backtesting.md | 75 +++++++++++++++++++++++++ docs/data-analysis.md | 1 + docs/strategy_analysis_example.md | 72 ------------------------ freqtrade/misc.py | 10 ++-- freqtrade/optimize/optimize_reports.py | 8 ++- tests/optimize/test_optimize_reports.py | 48 ++++++++++++++-- 6 files changed, 128 insertions(+), 86 deletions(-) create mode 100644 docs/advanced-backtesting.md diff --git a/docs/advanced-backtesting.md b/docs/advanced-backtesting.md new file mode 100644 index 000000000..9a7d71767 --- /dev/null +++ b/docs/advanced-backtesting.md @@ -0,0 +1,75 @@ +# Advanced Backtesting Analysis + +## Analyse the buy/entry and sell/exit tags + +It can be helpful to understand how a strategy behaves according to the buy/entry tags used to +mark up different buy conditions. You might want to see more complex statistics about each buy and +sell condition above those provided by the default backtesting output. You may also want to +determine indicator values on the signal candle that resulted in a trade opening. + +!!! Note + The following buy reason analysis is only available for backtesting, *not hyperopt*. + +We first need to enable the exporting of trades from backtesting: + +```bash +freqtrade backtesting -c --timeframe --strategy --timerange= --export=trades --export-filename=user_data/backtest_results/- +``` + +To analyse the buy tags, we need to use the `freqtrade tag-analysis` command. We need the signal +candles for each opened trade so add the following option to your config file: + +``` +'backtest_signal_candle_export_enable': true, +``` + +This will tell freqtrade to output a pickled dictionary of strategy, pairs and corresponding +DataFrame of the candles that resulted in buy signals. Depending on how many buys your strategy +makes, this file may get quite large, so periodically check your `user_data/backtest_results` +folder to delete old exports. + +Before running your next backtest, make sure you either delete your old backtest results or run +backtesting with the `--cache none` option to make sure no cached results are used. + +If all goes well, you should now see a `backtest-result-{timestamp}_signals.pkl` file in the +`user_data/backtest_results` folder. + +Now run the buy_reasons.py script, supplying a few options: + +```bash +freqtrade tag-analysis -c -s -t -g0,1,2,3,4 +``` + +The `-g` option is used to specify the various tabular outputs, ranging from the simplest (0) +to the most detailed per pair, per buy and per sell tag (4). More options are available by +running with the `-h` option. + +### Tuning the buy tags and sell tags to display + +To show only certain buy and sell tags in the displayed output, use the following two options: + +``` +--buy_reason_list : Comma separated list of buy signals to analyse. Default: "all" +--sell_reason_list : Comma separated list of sell signals to analyse. Default: "stop_loss,trailing_stop_loss" +``` + +For example: + +```bash +freqtrade tag-analysis -c -s -t -g0,1,2,3,4 --buy_reason_list "buy_tag_a,buy_tag_b" --sell_reason_list "roi,custom_sell_tag_a,stop_loss" +``` + +### Outputting signal candle indicators + +The real power of the buy_reasons.py script comes from the ability to print out the indicator +values present on signal candles to allow fine-grained investigation and tuning of buy signal +indicators. To print out a column for a given set of indicators, use the `--indicator-list` +option: + +```bash +freqtrade tag-analysis -c -s -t -g0,1,2,3,4 --buy_reason_list "buy_tag_a,buy_tag_b" --sell_reason_list "roi,custom_sell_tag_a,stop_loss" --indicator_list "rsi,rsi_1h,bb_lowerband,ema_9,macd,macdsignal" +``` + +The indicators have to be present in your strategy's main DataFrame (either for your main +timeframe or for informatives) otherwise they will simply be ignored in the script +output. diff --git a/docs/data-analysis.md b/docs/data-analysis.md index 9a79ee5ed..926ed3eae 100644 --- a/docs/data-analysis.md +++ b/docs/data-analysis.md @@ -122,5 +122,6 @@ Best avoid relative paths, since this starts at the storage location of the jupy * [Strategy debugging](strategy_analysis_example.md) - also available as Jupyter notebook (`user_data/notebooks/strategy_analysis_example.ipynb`) * [Plotting](plotting.md) +* [Tag Analysis](advanced-backtesting.md) Feel free to submit an issue or Pull Request enhancing this document if you would like to share ideas on how to best analyze the data. diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md index 48f54c824..ae0c6a6a3 100644 --- a/docs/strategy_analysis_example.md +++ b/docs/strategy_analysis_example.md @@ -250,75 +250,3 @@ fig.show() ``` Feel free to submit an issue or Pull Request enhancing this document if you would like to share ideas on how to best analyze the data. - -## Analyse the buy/entry and sell/exit tags - -It can be helpful to understand how a strategy behaves according to the buy/entry tags used to -mark up different buy conditions. You might want to see more complex statistics about each buy and -sell condition above those provided by the default backtesting output. You may also want to -determine indicator values on the signal candle that resulted in a trade opening. - -We first need to enable the exporting of trades from backtesting: - -``` -freqtrade backtesting -c --timeframe --strategy --timerange= --export=trades --export-filename=user_data/backtest_results/- -``` - -To analyse the buy tags, we need to use the buy_reasons.py script in the `scripts/` -folder. We need the signal candles for each opened trade so add the following option to your -config file: - -``` -'backtest_signal_candle_export_enable': true, -``` - -This will tell freqtrade to output a pickled dictionary of strategy, pairs and corresponding -DataFrame of the candles that resulted in buy signals. Depending on how many buys your strategy -makes, this file may get quite large, so periodically check your `user_data/backtest_results` -folder to delete old exports. - -Before running your next backtest, make sure you either delete your old backtest results or run -backtesting with the `--cache none` option to make sure no cached results are used. - -If all goes well, you should now see a `backtest-result-{timestamp}_signals.pkl` file in the -`user_data/backtest_results` folder. - -Now run the buy_reasons.py script, supplying a few options: - -``` -./scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 -``` - -The `-g` option is used to specify the various tabular outputs, ranging from the simplest (0) -to the most detailed per pair, per buy and per sell tag (4). More options are available by -running with the `-h` option. - -### Tuning the buy tags and sell tags to display - -To show only certain buy and sell tags in the displayed output, use the following two options: - -``` ---buy_reason_list : Comma separated list of buy signals to analyse. Default: "all" ---sell_reason_list : Comma separated list of sell signals to analyse. Default: "stop_loss,trailing_stop_loss" -``` - -For example: - -``` -./scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 --buy_reason_list "buy_tag_a,buy_tag_b" --sell_reason_list "roi,custom_sell_tag_a,stop_loss" -``` - -### Outputting signal candle indicators - -The real power of the buy_reasons.py script comes from the ability to print out the indicator -values present on signal candles to allow fine-grained investigation and tuning of buy signal -indicators. To print out a column for a given set of indicators, use the `--indicator-list` -option: - -``` -./scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 --buy_reason_list "buy_tag_a,buy_tag_b" --sell_reason_list "roi,custom_sell_tag_a,stop_loss" --indicator_list "rsi,rsi_1h,bb_lowerband,ema_9,macd,macdsignal" -``` - -The indicators have to be present in your strategy's main dataframe (either for your main -timeframe or for informatives) otherwise they will simply be ignored in the script -output. diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 9087ec6e2..be12d8224 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -4,7 +4,6 @@ Various tool function for Freqtrade and scripts import gzip import hashlib import logging -import pickle import re from copy import deepcopy from datetime import datetime @@ -13,6 +12,7 @@ from typing import Any, Iterator, List, Union from typing.io import IO from urllib.parse import urlparse +import joblib import rapidjson from freqtrade.constants import DECIMAL_PER_COIN_FALLBACK, DECIMALS_PER_COIN @@ -87,7 +87,7 @@ def file_dump_json(filename: Path, data: Any, is_zip: bool = False, log: bool = logger.debug(f'done json to "{filename}"') -def file_dump_pickle(filename: Path, data: Any, log: bool = True) -> None: +def file_dump_joblib(filename: Path, data: Any, log: bool = True) -> None: """ Dump object data into a file :param filename: file to create @@ -96,10 +96,10 @@ def file_dump_pickle(filename: Path, data: Any, log: bool = True) -> None: """ if log: - logger.info(f'dumping pickle to "{filename}"') + logger.info(f'dumping joblib to "{filename}"') with open(filename, 'wb') as fp: - pickle.dump(data, fp) - logger.debug(f'done pickling to "{filename}"') + joblib.dump(data, fp) + logger.debug(f'done joblib dump to "{filename}"') def json_load(datafile: IO) -> Any: diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 05eec693e..6288ee16a 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -11,7 +11,7 @@ from tabulate import tabulate from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN, UNLIMITED_STAKE_AMOUNT from freqtrade.data.btanalysis import (calculate_csum, calculate_market_change, calculate_max_drawdown) -from freqtrade.misc import (decimals_per_coin, file_dump_json, file_dump_pickle, +from freqtrade.misc import (decimals_per_coin, file_dump_joblib, file_dump_json, get_backtest_metadata_filename, round_coin_value) @@ -45,7 +45,7 @@ def store_backtest_stats(recordfilename: Path, stats: Dict[str, DataFrame]) -> N file_dump_json(latest_filename, {'latest_backtest': str(filename.name)}) -def store_backtest_signal_candles(recordfilename: Path, candles: Dict[str, Dict]) -> None: +def store_backtest_signal_candles(recordfilename: Path, candles: Dict[str, Dict]) -> Path: """ Stores backtest trade signal candles :param recordfilename: Path object, which can either be a filename or a directory. @@ -63,7 +63,9 @@ def store_backtest_signal_candles(recordfilename: Path, candles: Dict[str, Dict] f'{recordfilename.stem}-{datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}_signals.pkl' ) - file_dump_pickle(filename, candles) + file_dump_joblib(filename, candles) + + return filename def _get_line_floatfmt(stake_currency: str) -> List[str]: diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index d72cf4e86..ff8d420b3 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -2,6 +2,7 @@ import re from datetime import timedelta from pathlib import Path +import joblib import pandas as pd import pytest from arrow import Arrow @@ -204,23 +205,58 @@ def test_store_backtest_stats(testdatadir, mocker): def test_store_backtest_candles(testdatadir, mocker): - dump_mock = mocker.patch('freqtrade.optimize.optimize_reports.file_dump_pickle') + dump_mock = mocker.patch('freqtrade.optimize.optimize_reports.file_dump_joblib') - # test directory exporting - store_backtest_signal_candles(testdatadir, {'DefStrat': {'UNITTEST/BTC': pd.DataFrame()}}) + candle_dict = {'DefStrat': {'UNITTEST/BTC': pd.DataFrame()}} + + # mock directory exporting + store_backtest_signal_candles(testdatadir, candle_dict) assert dump_mock.call_count == 1 assert isinstance(dump_mock.call_args_list[0][0][0], Path) assert str(dump_mock.call_args_list[0][0][0]).endswith(str('_signals.pkl')) dump_mock.reset_mock() - # test file exporting - filename = testdatadir / 'testresult' - store_backtest_signal_candles(filename, {'DefStrat': {'UNITTEST/BTC': pd.DataFrame()}}) + # mock file exporting + filename = Path(testdatadir / 'testresult') + store_backtest_signal_candles(filename, candle_dict) assert dump_mock.call_count == 1 assert isinstance(dump_mock.call_args_list[0][0][0], Path) # result will be testdatadir / testresult-_signals.pkl assert str(dump_mock.call_args_list[0][0][0]).endswith(str('_signals.pkl')) + dump_mock.reset_mock() + + +def test_write_read_backtest_candles(tmpdir): + + candle_dict = {'DefStrat': {'UNITTEST/BTC': pd.DataFrame()}} + + # test directory exporting + stored_file = store_backtest_signal_candles(Path(tmpdir), candle_dict) + scp = open(stored_file, "rb") + pickled_signal_candles = joblib.load(scp) + scp.close() + + assert pickled_signal_candles.keys() == candle_dict.keys() + assert pickled_signal_candles['DefStrat'].keys() == pickled_signal_candles['DefStrat'].keys() + assert pickled_signal_candles['DefStrat']['UNITTEST/BTC'] \ + .equals(pickled_signal_candles['DefStrat']['UNITTEST/BTC']) + + _clean_test_file(stored_file) + + # test file exporting + filename = Path(tmpdir / 'testresult') + stored_file = store_backtest_signal_candles(filename, candle_dict) + scp = open(stored_file, "rb") + pickled_signal_candles = joblib.load(scp) + scp.close() + + assert pickled_signal_candles.keys() == candle_dict.keys() + assert pickled_signal_candles['DefStrat'].keys() == pickled_signal_candles['DefStrat'].keys() + assert pickled_signal_candles['DefStrat']['UNITTEST/BTC'] \ + .equals(pickled_signal_candles['DefStrat']['UNITTEST/BTC']) + + _clean_test_file(stored_file) def test_generate_pair_metrics(): From ea7fb4e6e6182c71aabcaf0fea9bf52214644b90 Mon Sep 17 00:00:00 2001 From: froggleston Date: Wed, 20 Apr 2022 13:51:45 +0100 Subject: [PATCH 121/254] Revert docs to buy_reasons script version --- docs/advanced-backtesting.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/advanced-backtesting.md b/docs/advanced-backtesting.md index 9a7d71767..d8c6c505e 100644 --- a/docs/advanced-backtesting.md +++ b/docs/advanced-backtesting.md @@ -13,11 +13,15 @@ determine indicator values on the signal candle that resulted in a trade opening We first need to enable the exporting of trades from backtesting: ```bash -freqtrade backtesting -c --timeframe --strategy --timerange= --export=trades --export-filename=user_data/backtest_results/- +freqtrade backtesting -c --timeframe --strategy --timerange= --export=trades ``` -To analyse the buy tags, we need to use the `freqtrade tag-analysis` command. We need the signal -candles for each opened trade so add the following option to your config file: +To analyse the buy tags, we need to use the `buy_reasons.py` script from +[froggleston's repo](https://github.com/froggleston/freqtrade-buyreasons). Follow the instructions +in their README to copy the script into your `freqtrade/scripts/` folder. + +We then need the signal candles for each opened trade so add the following option to your +config file: ``` 'backtest_signal_candle_export_enable': true, @@ -37,7 +41,7 @@ If all goes well, you should now see a `backtest-result-{timestamp}_signals.pkl` Now run the buy_reasons.py script, supplying a few options: ```bash -freqtrade tag-analysis -c -s -t -g0,1,2,3,4 +python3 scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 ``` The `-g` option is used to specify the various tabular outputs, ranging from the simplest (0) @@ -56,7 +60,7 @@ To show only certain buy and sell tags in the displayed output, use the followin For example: ```bash -freqtrade tag-analysis -c -s -t -g0,1,2,3,4 --buy_reason_list "buy_tag_a,buy_tag_b" --sell_reason_list "roi,custom_sell_tag_a,stop_loss" +python3 scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 --buy_reason_list "buy_tag_a,buy_tag_b" --sell_reason_list "roi,custom_sell_tag_a,stop_loss" ``` ### Outputting signal candle indicators @@ -67,7 +71,7 @@ indicators. To print out a column for a given set of indicators, use the `--indi option: ```bash -freqtrade tag-analysis -c -s -t -g0,1,2,3,4 --buy_reason_list "buy_tag_a,buy_tag_b" --sell_reason_list "roi,custom_sell_tag_a,stop_loss" --indicator_list "rsi,rsi_1h,bb_lowerband,ema_9,macd,macdsignal" +python3 scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 --buy_reason_list "buy_tag_a,buy_tag_b" --sell_reason_list "roi,custom_sell_tag_a,stop_loss" --indicator_list "rsi,rsi_1h,bb_lowerband,ema_9,macd,macdsignal" ``` The indicators have to be present in your strategy's main DataFrame (either for your main From 933054a51ccb991a646452e2e26f2ea7cad37229 Mon Sep 17 00:00:00 2001 From: froggleston Date: Wed, 20 Apr 2022 13:54:50 +0100 Subject: [PATCH 122/254] Move enable option text to make better sense --- docs/advanced-backtesting.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/advanced-backtesting.md b/docs/advanced-backtesting.md index d8c6c505e..4d91c4305 100644 --- a/docs/advanced-backtesting.md +++ b/docs/advanced-backtesting.md @@ -10,7 +10,15 @@ determine indicator values on the signal candle that resulted in a trade opening !!! Note The following buy reason analysis is only available for backtesting, *not hyperopt*. -We first need to enable the exporting of trades from backtesting: +We first need to tell freqtrade to export the signal candles for each opened trade, +so add the following option to your config file: + +``` +'backtest_signal_candle_export_enable': true, +``` + +We then need to run backtesting and include the `--export` option to enable the exporting of +trades: ```bash freqtrade backtesting -c --timeframe --strategy --timerange= --export=trades @@ -20,13 +28,6 @@ To analyse the buy tags, we need to use the `buy_reasons.py` script from [froggleston's repo](https://github.com/froggleston/freqtrade-buyreasons). Follow the instructions in their README to copy the script into your `freqtrade/scripts/` folder. -We then need the signal candles for each opened trade so add the following option to your -config file: - -``` -'backtest_signal_candle_export_enable': true, -``` - This will tell freqtrade to output a pickled dictionary of strategy, pairs and corresponding DataFrame of the candles that resulted in buy signals. Depending on how many buys your strategy makes, this file may get quite large, so periodically check your `user_data/backtest_results` From f92997d3789c6eb8b9116e4a55dac2b9b7112aa7 Mon Sep 17 00:00:00 2001 From: froggleston Date: Wed, 20 Apr 2022 14:05:33 +0100 Subject: [PATCH 123/254] Move signal candle generation into separate function --- freqtrade/optimize/backtesting.py | 37 +++++++++++++++++-------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 03a6cade0..9d7f19f7a 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -1079,26 +1079,29 @@ class Backtesting: if self.backtest_signal_candle_export_enable and \ self.dataprovider.runmode == RunMode.BACKTEST: - signal_candles_only = {} - for pair in preprocessed_tmp.keys(): - signal_candles_only_df = DataFrame() - - pairdf = preprocessed_tmp[pair] - resdf = results['results'] - pairresults = resdf.loc[(resdf["pair"] == pair)] - - if pairdf.shape[0] > 0: - for t, v in pairresults.open_date.items(): - allinds = pairdf.loc[(pairdf['date'] < v)] - signal_inds = allinds.iloc[[-1]] - signal_candles_only_df = signal_candles_only_df.append(signal_inds) - - signal_candles_only[pair] = signal_candles_only_df - - self.processed_dfs[self.strategy.get_strategy_name()] = signal_candles_only + self._generate_trade_signal_candles(preprocessed_tmp, results) return min_date, max_date + def _generate_trade_signal_candles(self, preprocessed_df, bt_results): + signal_candles_only = {} + for pair in preprocessed_df.keys(): + signal_candles_only_df = DataFrame() + + pairdf = preprocessed_df[pair] + resdf = bt_results['results'] + pairresults = resdf.loc[(resdf["pair"] == pair)] + + if pairdf.shape[0] > 0: + for t, v in pairresults.open_date.items(): + allinds = pairdf.loc[(pairdf['date'] < v)] + signal_inds = allinds.iloc[[-1]] + signal_candles_only_df = signal_candles_only_df.append(signal_inds) + + signal_candles_only[pair] = signal_candles_only_df + + self.processed_dfs[self.strategy.get_strategy_name()] = signal_candles_only + def _get_min_cached_backtest_date(self): min_backtest_date = None backtest_cache_age = self.config.get('backtest_cache', constants.BACKTEST_CACHE_DEFAULT) From ba305e93edb49defc42f9a8b01b957e33984722f Mon Sep 17 00:00:00 2001 From: Patel Kaushal <36811899+koradiyakaushal@users.noreply.github.com> Date: Thu, 21 Apr 2022 18:35:41 +0530 Subject: [PATCH 124/254] Ref: timeseries friendly merge_ordered in merge_informative_pair function --- freqtrade/strategy/strategy_helper.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index a36cb3dbb..fef5fb812 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -56,12 +56,18 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame, # Combine the 2 dataframes # all indicators on the informative sample MUST be calculated before this point - dataframe = pd.merge(dataframe, informative, left_on='date', - right_on=date_merge, how='left') + if ffill: + # https://pandas.pydata.org/docs/user_guide/merging.html#timeseries-friendly-merging + # merge_ordered - ffill method is 2.5x faster than seperate ffill() + dataframe = pd.merge_ordered(dataframe, informative, fill_method="ffill", left_on='date', + right_on=date_merge, how='left') + else: + dataframe = pd.merge(dataframe, informative, left_on='date', + right_on=date_merge, how='left') dataframe = dataframe.drop(date_merge, axis=1) - if ffill: - dataframe = dataframe.ffill() + # if ffill: + # dataframe = dataframe.ffill() return dataframe From 7f60364f63057bba06dfc3bf3ed3886560906a3b Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 22 Apr 2022 06:38:51 +0200 Subject: [PATCH 125/254] Add doc-page to index --- docs/advanced-backtesting.md | 12 ++++++------ freqtrade/optimize/backtesting.py | 8 ++++---- mkdocs.yml | 1 + 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/advanced-backtesting.md b/docs/advanced-backtesting.md index 4d91c4305..69dc428f1 100644 --- a/docs/advanced-backtesting.md +++ b/docs/advanced-backtesting.md @@ -1,6 +1,6 @@ # Advanced Backtesting Analysis -## Analyse the buy/entry and sell/exit tags +## Analyze the buy/entry and sell/exit tags It can be helpful to understand how a strategy behaves according to the buy/entry tags used to mark up different buy conditions. You might want to see more complex statistics about each buy and @@ -20,11 +20,11 @@ so add the following option to your config file: We then need to run backtesting and include the `--export` option to enable the exporting of trades: -```bash +``` bash freqtrade backtesting -c --timeframe --strategy --timerange= --export=trades ``` -To analyse the buy tags, we need to use the `buy_reasons.py` script from +To analyze the buy tags, we need to use the `buy_reasons.py` script from [froggleston's repo](https://github.com/froggleston/freqtrade-buyreasons). Follow the instructions in their README to copy the script into your `freqtrade/scripts/` folder. @@ -39,9 +39,9 @@ backtesting with the `--cache none` option to make sure no cached results are us If all goes well, you should now see a `backtest-result-{timestamp}_signals.pkl` file in the `user_data/backtest_results` folder. -Now run the buy_reasons.py script, supplying a few options: +Now run the `buy_reasons.py` script, supplying a few options: -```bash +``` bash python3 scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 ``` @@ -76,5 +76,5 @@ python3 scripts/buy_reasons.py -c -s -t Date: Fri, 22 Apr 2022 18:46:12 +0100 Subject: [PATCH 126/254] Add signals enum to 'export' cli option --- freqtrade/commands/arguments.py | 2 +- freqtrade/constants.py | 3 +-- freqtrade/optimize/backtesting.py | 5 +++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 7d4624bd1..8a108fe79 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -23,7 +23,7 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv", ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", "enable_protections", "dry_run_wallet", "timeframe_detail", - "strategy_list", "export", "exportfilename", + "strategy_list", "export", "exportfilename" "backtest_breakdown", "backtest_cache"] ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", diff --git a/freqtrade/constants.py b/freqtrade/constants.py index d21020a3f..1a21ec77f 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -14,7 +14,7 @@ PROCESS_THROTTLE_SECS = 5 # sec HYPEROPT_EPOCH = 100 # epochs RETRY_TIMEOUT = 30 # sec TIMEOUT_UNITS = ['minutes', 'seconds'] -EXPORT_OPTIONS = ['none', 'trades'] +EXPORT_OPTIONS = ['none', 'trades', 'signals'] DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite' DEFAULT_DB_DRYRUN_URL = 'sqlite:///tradesv3.dryrun.sqlite' UNLIMITED_STAKE_AMOUNT = 'unlimited' @@ -380,7 +380,6 @@ CONF_SCHEMA = { }, 'position_adjustment_enable': {'type': 'boolean'}, 'max_entry_position_adjustment': {'type': ['integer', 'number'], 'minimum': -1}, - 'backtest_signal_candle_export_enable': {'type': 'boolean'}, }, 'definitions': { 'exchange': { diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 9d7f19f7a..c2d9f1edb 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -1077,7 +1077,7 @@ class Backtesting: }) self.all_results[self.strategy.get_strategy_name()] = results - if self.backtest_signal_candle_export_enable and \ + if self.config.get('export', 'none') == 'signals' and \ self.dataprovider.runmode == RunMode.BACKTEST: self._generate_trade_signal_candles(preprocessed_tmp, results) @@ -1163,8 +1163,9 @@ class Backtesting: if self.config.get('export', 'none') == 'trades': store_backtest_stats(self.config['exportfilename'], self.results) - if self.backtest_signal_candle_export_enable and \ + if self.config.get('export', 'none') == 'signals' and \ self.dataprovider.runmode == RunMode.BACKTEST: + store_backtest_stats(self.config['exportfilename'], self.results) store_backtest_signal_candles(self.config['exportfilename'], self.processed_dfs) # Results may be mixed up now. Sort them so they follow --strategy-list order. From 2fc4e5e1172917368719fc82ec16e537298d55dc Mon Sep 17 00:00:00 2001 From: froggleston Date: Fri, 22 Apr 2022 18:54:02 +0100 Subject: [PATCH 127/254] Fix weird removal of comma --- freqtrade/commands/arguments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 8a108fe79..7d4624bd1 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -23,7 +23,7 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv", ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", "enable_protections", "dry_run_wallet", "timeframe_detail", - "strategy_list", "export", "exportfilename" + "strategy_list", "export", "exportfilename", "backtest_breakdown", "backtest_cache"] ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", From dff9d52b3067a843e48c57e56e70a3942611f88d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 08:51:52 +0200 Subject: [PATCH 128/254] Remove hints on no longer used option, add very primitive test --- docs/backtesting.md | 24 ++++++++++++------------ docs/configuration.md | 1 - freqtrade/optimize/backtesting.py | 11 ++++------- tests/optimize/test_backtesting.py | 5 ++++- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 5d836d01b..f732068f1 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -20,7 +20,8 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH] [--dry-run-wallet DRY_RUN_WALLET] [--timeframe-detail TIMEFRAME_DETAIL] [--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]] - [--export {none,trades}] [--export-filename PATH] + [--export {none,trades,signals}] + [--export-filename PATH] [--breakdown {day,week,month} [{day,week,month} ...]] [--cache {none,day,week,month}] @@ -63,18 +64,17 @@ optional arguments: `30m`, `1h`, `1d`). --strategy-list STRATEGY_LIST [STRATEGY_LIST ...] Provide a space-separated list of strategies to - backtest. Please note that timeframe needs to be - set either in config or via command line. When using - this together with `--export trades`, the strategy- - name is injected into the filename (so `backtest- - data.json` becomes `backtest-data-SampleStrategy.json` - --export {none,trades} + backtest. Please note that timeframe needs to be set + either in config or via command line. When using this + together with `--export trades`, the strategy-name is + injected into the filename (so `backtest-data.json` + becomes `backtest-data-SampleStrategy.json` + --export {none,trades,signals} Export backtest results (default: trades). - --export-filename PATH - Save backtest results to the file with this filename. - Requires `--export` to be set as well. Example: - `--export-filename=user_data/backtest_results/backtest - _today.json` + --export-filename PATH, --backtest-filename PATH + Use this filename for backtest results.Requires + `--export` to be set as well. Example: `--export-filen + ame=user_data/backtest_results/backtest_today.json` --breakdown {day,week,month} [{day,week,month} ...] Show backtesting breakdown per [day, week, month]. --cache {none,day,week,month} diff --git a/docs/configuration.md b/docs/configuration.md index 061b6c77c..5770450a6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -229,7 +229,6 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `dataformat_trades` | Data format to use to store historical trades data.
*Defaults to `jsongz`*.
**Datatype:** String | `position_adjustment_enable` | Enables the strategy to use position adjustments (additional buys or sells). [More information here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean | `max_entry_position_adjustment` | Maximum additional order(s) for each open trade on top of the first entry Order. Set it to `-1` for unlimited additional orders. [More information here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `-1`.*
**Datatype:** Positive Integer or -1 -| `backtest_signal_candle_export_enable` | Enables the exporting of signal candles for use in post-backtesting analysis of buy tags. See [Strategy Analysis](strategy_analysis_example.md#analyse-the-buy-entry-and-sell-exit-tags).
*Defaults to `false`.*
**Datatype:** Boolean ### Parameters in the strategy diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index c2d9f1edb..f4149fdc1 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -131,9 +131,6 @@ class Backtesting: self.config['startup_candle_count'] = self.required_startup self.exchange.validate_required_startup_candles(self.required_startup, self.timeframe) - self.backtest_signal_candle_export_enable = self.config.get( - 'backtest_signal_candle_export_enable', False) - self.trading_mode: TradingMode = config.get('trading_mode', TradingMode.SPOT) # strategies which define "can_short=True" will fail to load in Spot mode. self._can_short = self.trading_mode != TradingMode.SPOT @@ -1077,8 +1074,8 @@ class Backtesting: }) self.all_results[self.strategy.get_strategy_name()] = results - if self.config.get('export', 'none') == 'signals' and \ - self.dataprovider.runmode == RunMode.BACKTEST: + if (self.config.get('export', 'none') == 'signals' and + self.dataprovider.runmode == RunMode.BACKTEST): self._generate_trade_signal_candles(preprocessed_tmp, results) return min_date, max_date @@ -1163,8 +1160,8 @@ class Backtesting: if self.config.get('export', 'none') == 'trades': store_backtest_stats(self.config['exportfilename'], self.results) - if self.config.get('export', 'none') == 'signals' and \ - self.dataprovider.runmode == RunMode.BACKTEST: + if (self.config.get('export', 'none') == 'signals' and + self.dataprovider.runmode == RunMode.BACKTEST): store_backtest_stats(self.config['exportfilename'], self.results) store_backtest_signal_candles(self.config['exportfilename'], self.processed_dfs) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 08957acf9..797d3bafa 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -384,14 +384,16 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None: mocker.patch('freqtrade.optimize.backtesting.generate_backtest_stats') mocker.patch('freqtrade.optimize.backtesting.show_backtest_results') sbs = mocker.patch('freqtrade.optimize.backtesting.store_backtest_stats') + sbc = mocker.patch('freqtrade.optimize.backtesting.store_backtest_signal_candles') mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', PropertyMock(return_value=['UNITTEST/BTC'])) default_conf['timeframe'] = '1m' default_conf['datadir'] = testdatadir - default_conf['export'] = 'trades' + default_conf['export'] = 'signals' default_conf['exportfilename'] = 'export.txt' default_conf['timerange'] = '-1510694220' + default_conf['runmode'] = RunMode.BACKTEST backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) @@ -407,6 +409,7 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None: assert backtesting.strategy.dp._pairlists is not None assert backtesting.strategy.bot_loop_start.call_count == 1 assert sbs.call_count == 1 + assert sbc.call_count == 1 def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) -> None: From c6927a1501d76b4e8b61ced9f5d1c53dbee75006 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 09:10:15 +0200 Subject: [PATCH 129/254] Fix argument spelling --- freqtrade/commands/arguments.py | 3 ++- freqtrade/commands/cli_options.py | 4 ++-- freqtrade/resolvers/iresolver.py | 9 ++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 2fb8d3258..e66f100c0 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -37,7 +37,8 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"] -ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column", "print_colorized"] +ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column", "print_colorized", + "recursive_strategy_search"] ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column", "print_colorized"] diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 095aad6c3..548a7f473 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -83,9 +83,9 @@ AVAILABLE_CLI_OPTIONS = { action='store_true', ), "recursive_strategy_search": Arg( - '--recursive_strategy_search', + '--recursive-strategy-search', help='Recursively search for a strategy in the strategies folder.', - metavar='store_true', + action='store_true', ), # Main options "strategy": Arg( diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index cddc8b84d..d310856d8 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -44,7 +44,7 @@ class IResolver: @classmethod def build_search_paths(cls, config: Dict[str, Any], user_subdir: Optional[str] = None, - extra_dirs: Optional[List[str]] = None) -> List[Path]: + extra_dirs: List[str] = []) -> List[Path]: abs_paths: List[Path] = [] if cls.initial_search_path: @@ -53,10 +53,9 @@ class IResolver: if user_subdir: abs_paths.insert(0, config['user_data_dir'].joinpath(user_subdir)) - if extra_dirs: - # Add extra directory to the top of the search paths - for dir in extra_dirs: - abs_paths.insert(0, Path(dir).resolve()) + # Add extra directory to the top of the search paths + for dir in extra_dirs: + abs_paths.insert(0, Path(dir).resolve()) return abs_paths From ba92e09b7bab024fb7ea7a12d1b3df22cf33fc6a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 09:11:50 +0200 Subject: [PATCH 130/254] list-strategies should find recursively as well --- config_examples/config_full.example.json | 1 + freqtrade/commands/list_commands.py | 9 +++++---- freqtrade/optimize/hyperopt_tools.py | 3 ++- freqtrade/resolvers/iresolver.py | 11 +++++++++-- freqtrade/resolvers/strategy_resolver.py | 2 +- freqtrade/rpc/api_server/api_v1.py | 3 ++- 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json index 7931476b4..8f14e1771 100644 --- a/config_examples/config_full.example.json +++ b/config_examples/config_full.example.json @@ -179,6 +179,7 @@ "disable_dataframe_checks": false, "strategy": "SampleStrategy", "strategy_path": "user_data/strategies/", + "recursive_strategy_search": false, "dataformat_ohlcv": "json", "dataformat_trades": "jsongz" } diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py index 38fb098a0..1833db922 100644 --- a/freqtrade/commands/list_commands.py +++ b/freqtrade/commands/list_commands.py @@ -41,7 +41,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None: print(tabulate(exchanges, headers=['Exchange name', 'Valid', 'reason'])) -def _print_objs_tabular(objs: List, print_colorized: bool) -> None: +def _print_objs_tabular(objs: List, print_colorized: bool, base_dir: Path) -> None: if print_colorized: colorama_init(autoreset=True) red = Fore.RED @@ -55,7 +55,7 @@ def _print_objs_tabular(objs: List, print_colorized: bool) -> None: names = [s['name'] for s in objs] objs_to_print = [{ 'name': s['name'] if s['name'] else "--", - 'location': s['location'].name, + 'location': s['location'].relative_to(base_dir), 'status': (red + "LOAD FAILED" + reset if s['class'] is None else "OK" if names.count(s['name']) == 1 else yellow + "DUPLICATE NAME" + reset) @@ -77,7 +77,8 @@ def start_list_strategies(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) directory = Path(config.get('strategy_path', config['user_data_dir'] / USERPATH_STRATEGIES)) - strategy_objs = StrategyResolver.search_all_objects(directory, not args['print_one_column']) + strategy_objs = StrategyResolver.search_all_objects( + directory, not args['print_one_column'], config.get('recursive_strategy_search', False)) # Sort alphabetically strategy_objs = sorted(strategy_objs, key=lambda x: x['name']) for obj in strategy_objs: @@ -89,7 +90,7 @@ def start_list_strategies(args: Dict[str, Any]) -> None: if args['print_one_column']: print('\n'.join([s['name'] for s in strategy_objs])) else: - _print_objs_tabular(strategy_objs, config.get('print_colorized', False)) + _print_objs_tabular(strategy_objs, config.get('print_colorized', False), directory) def start_list_timeframes(args: Dict[str, Any]) -> None: diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py index 8c84f772a..e836681c5 100755 --- a/freqtrade/optimize/hyperopt_tools.py +++ b/freqtrade/optimize/hyperopt_tools.py @@ -41,7 +41,8 @@ class HyperoptTools(): """ from freqtrade.resolvers.strategy_resolver import StrategyResolver directory = Path(config.get('strategy_path', config['user_data_dir'] / USERPATH_STRATEGIES)) - strategy_objs = StrategyResolver.search_all_objects(directory, False) + strategy_objs = StrategyResolver.search_all_objects( + directory, False, config.get('recursive_strategy_search', False)) strategies = [s for s in strategy_objs if s['name'] == strategy_name] if strategies: strategy = strategies[0] diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index d310856d8..74b28dffe 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -182,18 +182,25 @@ class IResolver: ) @classmethod - def search_all_objects(cls, directory: Path, - enum_failed: bool) -> List[Dict[str, Any]]: + def search_all_objects(cls, directory: Path, enum_failed: bool, + recursive: bool = False) -> List[Dict[str, Any]]: """ Searches a directory for valid objects :param directory: Path to search :param enum_failed: If True, will return None for modules which fail. Otherwise, failing modules are skipped. + :param recursive: Recursively walk directory tree searching for strategies :return: List of dicts containing 'name', 'class' and 'location' entries """ logger.debug(f"Searching for {cls.object_type.__name__} '{directory}'") objects = [] for entry in directory.iterdir(): + if ( + recursive and entry.is_dir() + and not entry.name.startswith('__') + and not entry.name.startswith('.') + ): + objects.extend(cls.search_all_objects(entry, enum_failed, recursive=recursive)) # Only consider python files if entry.suffix != '.py': logger.debug('Ignoring %s', entry) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 8a22dbd65..60961b15b 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -167,7 +167,7 @@ class StrategyResolver(IResolver): :param extra_dir: additional directory to search for the given strategy :return: Strategy instance or None """ - if 'recursive_strategy_search' in config and config['recursive_strategy_search']: + if config.get('recursive_strategy_search', False): extra_dirs: List[str] = [ path[0] for path in walk(f"{config['user_data_dir']}/{USERPATH_STRATEGIES}") ] # sub-directories diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 5a34385da..fe6426178 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -243,7 +243,8 @@ def list_strategies(config=Depends(get_config)): directory = Path(config.get( 'strategy_path', config['user_data_dir'] / USERPATH_STRATEGIES)) from freqtrade.resolvers.strategy_resolver import StrategyResolver - strategies = StrategyResolver.search_all_objects(directory, False) + strategies = StrategyResolver.search_all_objects( + directory, False, config.get('recursive_strategy_search', False)) strategies = sorted(strategies, key=lambda x: x['name']) return {'strategies': [x['name'] for x in strategies]} From aa5345190ec36bb20d03cc66c16c958c371b9214 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 09:19:18 +0200 Subject: [PATCH 131/254] Test recursive strategy-listing --- tests/commands/test_commands.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 1431bd22a..39294e568 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -892,6 +892,26 @@ def test_start_list_strategies(mocker, caplog, capsys): assert "legacy_strategy_v1.py" in captured.out assert CURRENT_TEST_STRATEGY in captured.out assert "LOAD FAILED" in captured.out + # Recursive + assert "TestStrategyNoImplements" not in captured.out + + # Test recursive + args = [ + "list-strategies", + "--strategy-path", + str(Path(__file__).parent.parent / "strategy" / "strats"), + '--no-color', + '--recursive-strategy-search' + ] + pargs = get_args(args) + # pargs['config'] = None + start_list_strategies(pargs) + captured = capsys.readouterr() + assert "TestStrategyLegacyV1" in captured.out + assert "legacy_strategy_v1.py" in captured.out + assert "StrategyTestV2" in captured.out + assert "TestStrategyNoImplements" in captured.out + assert "broken_strats/broken_futures_strategies.py" in captured.out def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys): From 580a2c6545ce79ffda7b54f2e1a928426d519aef Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 09:23:53 +0200 Subject: [PATCH 132/254] Don't repeat backtest-storing --- freqtrade/optimize/backtesting.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index f4149fdc1..f5571c4e2 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -1157,12 +1157,11 @@ class Backtesting: else: self.results = results - if self.config.get('export', 'none') == 'trades': + if self.config.get('export', 'none') in ('trades', 'signals'): store_backtest_stats(self.config['exportfilename'], self.results) if (self.config.get('export', 'none') == 'signals' and self.dataprovider.runmode == RunMode.BACKTEST): - store_backtest_stats(self.config['exportfilename'], self.results) store_backtest_signal_candles(self.config['exportfilename'], self.processed_dfs) # Results may be mixed up now. Sort them so they follow --strategy-list order. From 5a90d5ece67dc76866bb675fbe7a7f3f6ddbbaa2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 09:44:04 +0200 Subject: [PATCH 133/254] Fix docstring quotes --- 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 babcc5491..78d237c8d 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -410,7 +410,7 @@ class Hyperopt: dump(preprocessed, self.data_pickle_file) 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 Steps: @@ -420,7 +420,7 @@ class Hyperopt: 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` - ''' + """ def unique_list(a_list): new_list = [] for item in a_list: From 30f314d580807bdb83969981293c162bdafc9a76 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 10:44:11 +0200 Subject: [PATCH 134/254] windows compatibility of test --- tests/commands/test_commands.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 39294e568..d1f54ad52 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -847,7 +847,7 @@ def test_start_convert_trades(mocker, caplog): assert convert_mock.call_count == 1 -def test_start_list_strategies(mocker, caplog, capsys): +def test_start_list_strategies(capsys): args = [ "list-strategies", @@ -911,7 +911,7 @@ def test_start_list_strategies(mocker, caplog, capsys): assert "legacy_strategy_v1.py" in captured.out assert "StrategyTestV2" in captured.out assert "TestStrategyNoImplements" in captured.out - assert "broken_strats/broken_futures_strategies.py" in captured.out + assert str(Path("broken_strats/broken_futures_strategies.py")) in captured.out def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys): From 84f5a4d5bc0e116ba1b7a5a476c9d4e717561cf3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 10:51:24 +0200 Subject: [PATCH 135/254] Fix indentation --- freqtrade/strategy/strategy_helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py index fef5fb812..43728dc1f 100644 --- a/freqtrade/strategy/strategy_helper.py +++ b/freqtrade/strategy/strategy_helper.py @@ -60,10 +60,10 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame, # https://pandas.pydata.org/docs/user_guide/merging.html#timeseries-friendly-merging # merge_ordered - ffill method is 2.5x faster than seperate ffill() dataframe = pd.merge_ordered(dataframe, informative, fill_method="ffill", left_on='date', - right_on=date_merge, how='left') + right_on=date_merge, how='left') else: dataframe = pd.merge(dataframe, informative, left_on='date', - right_on=date_merge, how='left') + right_on=date_merge, how='left') dataframe = dataframe.drop(date_merge, axis=1) # if ffill: From 1120392f39f5e0f9fde546592e2a7e0b330f9e25 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 11:12:43 +0200 Subject: [PATCH 136/254] Fix pre-commit indentation --- .github/ISSUE_TEMPLATE/feature_request.md | 1 - .github/workflows/docker_update_readme.yml | 1 - .pre-commit-config.yaml | 34 +++++++++++++++++----- .pylintrc | 1 - docs/javascripts/config.js | 2 +- freqtrade.service | 1 - freqtrade.service.watchdog | 1 - requirements-plot.txt | 1 - 8 files changed, 27 insertions(+), 15 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index c32fb33c2..a18915462 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -24,4 +24,3 @@ Have you search for this feature before requesting it? It's highly likely that a ## Describe the enhancement *Explain the enhancement you would like* - diff --git a/.github/workflows/docker_update_readme.yml b/.github/workflows/docker_update_readme.yml index 822533ee2..4587626f6 100644 --- a/.github/workflows/docker_update_readme.yml +++ b/.github/workflows/docker_update_readme.yml @@ -15,4 +15,3 @@ jobs: DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }} DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} DOCKERHUB_REPOSITORY: freqtradeorg/freqtrade - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 31af5b7c7..316baf0e3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,21 +1,39 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: -- repo: https://github.com/pycqa/flake8 - rev: '4.0.1' + - repo: https://github.com/pycqa/flake8 + rev: "4.0.1" hooks: - - id: flake8 + - id: flake8 # stages: [push] -- repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v0.942' + - repo: https://github.com/pre-commit/mirrors-mypy + rev: "v0.942" hooks: - - id: mypy + - id: mypy # stages: [push] -- repo: https://github.com/pycqa/isort - rev: '5.10.1' + - repo: https://github.com/pycqa/isort + rev: "5.10.1" hooks: - id: isort name: isort (python) # stages: [push] + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.4.0 + hooks: + - id: end-of-file-fixer + exclude: | + (?x)^( + tests/.*| + .*\.svg + )$ + - id: mixed-line-ending + - id: debug-statements + - id: check-ast + - id: trailing-whitespace + exclude: | + (?x)^( + .*\.md + )$ diff --git a/.pylintrc b/.pylintrc index dce99c067..0932ecba4 100644 --- a/.pylintrc +++ b/.pylintrc @@ -7,4 +7,3 @@ ignore=vendor [TYPECHECK] ignored-modules=numpy,talib,talib.abstract - diff --git a/docs/javascripts/config.js b/docs/javascripts/config.js index 95d619efc..80e81ba59 100644 --- a/docs/javascripts/config.js +++ b/docs/javascripts/config.js @@ -9,4 +9,4 @@ window.MathJax = { ignoreHtmlClass: ".*|", processHtmlClass: "arithmatex" } -}; \ No newline at end of file +}; diff --git a/freqtrade.service b/freqtrade.service index df220ed39..6f0c73ee4 100644 --- a/freqtrade.service +++ b/freqtrade.service @@ -11,4 +11,3 @@ Restart=on-failure [Install] WantedBy=default.target - diff --git a/freqtrade.service.watchdog b/freqtrade.service.watchdog index 66ea00d76..dcd32ae18 100644 --- a/freqtrade.service.watchdog +++ b/freqtrade.service.watchdog @@ -27,4 +27,3 @@ WatchdogSec=20 [Install] WantedBy=default.target - diff --git a/requirements-plot.txt b/requirements-plot.txt index 9eb6a10a3..d9faed301 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -2,4 +2,3 @@ -r requirements.txt plotly==5.7.0 - From a2af7b4fd82ef5b1697aa427967a70cfff53f876 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 11:25:20 +0200 Subject: [PATCH 137/254] Test non-ffill approach --- tests/strategy/test_strategy_helpers.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/strategy/test_strategy_helpers.py b/tests/strategy/test_strategy_helpers.py index 65fb9f6dc..244fd3919 100644 --- a/tests/strategy/test_strategy_helpers.py +++ b/tests/strategy/test_strategy_helpers.py @@ -68,6 +68,21 @@ def test_merge_informative_pair(): assert result.iloc[7]['date_1h'] == result.iloc[4]['date'] assert result.iloc[8]['date_1h'] == result.iloc[4]['date'] + informative = generate_test_data('1h', 40) + result = merge_informative_pair(data, informative, '15m', '1h', ffill=False) + # First 3 rows are empty + assert result.iloc[0]['date_1h'] is pd.NaT + assert result.iloc[1]['date_1h'] is pd.NaT + assert result.iloc[2]['date_1h'] is pd.NaT + # Next 4 rows contain the starting date (0:00) + assert result.iloc[3]['date_1h'] == result.iloc[0]['date'] + assert result.iloc[4]['date_1h'] is pd.NaT + assert result.iloc[5]['date_1h'] is pd.NaT + assert result.iloc[6]['date_1h'] is pd.NaT + # Next 4 rows contain the next Hourly date original date row 4 + assert result.iloc[7]['date_1h'] == result.iloc[4]['date'] + assert result.iloc[8]['date_1h'] is pd.NaT + def test_merge_informative_pair_same(): data = generate_test_data('15m', 40) From f2912f88150e70256d163d904652862c4c398b8e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 11:31:12 +0200 Subject: [PATCH 138/254] Improve mypy runs --- freqtrade/optimize/hyperopt.py | 6 +++--- freqtrade/optimize/optimize_reports.py | 2 +- setup.cfg | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 78d237c8d..3ae975ca7 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -290,7 +290,7 @@ class Hyperopt: self.assign_params(params_dict, 'protection') if HyperoptTools.has_space(self.config, 'roi'): - self.backtesting.strategy.minimal_roi = ( # type: ignore + self.backtesting.strategy.minimal_roi = ( self.custom_hyperopt.generate_roi_table(params_dict)) if HyperoptTools.has_space(self.config, 'stoploss'): @@ -465,8 +465,8 @@ class Hyperopt: # We don't need exchange instance anymore while running hyperopt self.backtesting.exchange.close() - self.backtesting.exchange._api = None # type: ignore - self.backtesting.exchange._api_async = None # type: ignore + self.backtesting.exchange._api = None + self.backtesting.exchange._api_async = None self.backtesting.exchange.loop = None # type: ignore # self.backtesting.exchange = None # type: ignore self.backtesting.pairlists = None # type: ignore diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 6288ee16a..0ceb3a411 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -264,7 +264,7 @@ def generate_edge_table(results: dict) -> str: # Ignore type as floatfmt does allow tuples but mypy does not know that return tabulate(tabular_data, headers=headers, - floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore + floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") def _get_resample_from_period(period: str) -> str: diff --git a/setup.cfg b/setup.cfg index f4a90bda7..a33ceda1f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -52,6 +52,7 @@ exclude = [mypy] ignore_missing_imports = True +warn_unused_ignores = True [mypy-tests.*] ignore_errors = True From c1a7fc873d5c447c1e985e17ba1773c24582f47c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 11:47:10 +0200 Subject: [PATCH 139/254] Speed up ci by running coverage only where necessary --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8df7ab10..4e18127fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -157,6 +157,12 @@ jobs: pip install -e . - name: Tests + if: (runner.os != 'Linux' || matrix.python-version != '3.8') + run: | + pytest --random-order + + - name: Tests (with cov) + if: (runner.os == 'Linux' && matrix.python-version == '3.8') run: | pytest --random-order --cov=freqtrade --cov-config=.coveragerc From 3586c2e984845b95189cd2e0efc20fbe76425334 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 14:22:06 +0200 Subject: [PATCH 140/254] Windows no random order --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e18127fb..1902a6c45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -235,7 +235,7 @@ jobs: - name: Tests run: | - pytest --random-order --cov=freqtrade --cov-config=.coveragerc + pytest --random-order - name: Backtesting run: | From 12d03e6a91dbc2e383d071540455382d0ba953d4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 14:53:47 +0200 Subject: [PATCH 141/254] Remove unused test methods --- tests/conftest.py | 42 ---------------------------------------- tests/edge/test_edge.py | 43 ----------------------------------------- 2 files changed, 85 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index b1dcdbbd7..cc07de1de 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1632,40 +1632,6 @@ def limit_buy_order(limit_buy_order_open): return order -@pytest.fixture(scope='function') -def market_buy_order(): - return { - 'id': 'mocked_market_buy', - 'type': 'market', - 'side': 'buy', - 'symbol': 'mocked', - 'timestamp': arrow.utcnow().int_timestamp * 1000, - 'datetime': arrow.utcnow().isoformat(), - 'price': 0.00004099, - 'amount': 91.99181073, - 'filled': 91.99181073, - 'remaining': 0.0, - 'status': 'closed' - } - - -@pytest.fixture -def market_sell_order(): - return { - 'id': 'mocked_limit_sell', - 'type': 'market', - 'side': 'sell', - 'symbol': 'mocked', - 'timestamp': arrow.utcnow().int_timestamp * 1000, - 'datetime': arrow.utcnow().isoformat(), - 'price': 0.00004173, - 'amount': 91.99181073, - 'filled': 91.99181073, - 'remaining': 0.0, - 'status': 'closed' - } - - @pytest.fixture def limit_buy_order_old(): return { @@ -2946,14 +2912,6 @@ def limit_order(limit_buy_order_usdt, limit_sell_order_usdt): } -@pytest.fixture(scope='function') -def market_order(market_buy_order_usdt, market_sell_order_usdt): - return { - 'buy': market_buy_order_usdt, - 'sell': market_sell_order_usdt - } - - @pytest.fixture(scope='function') def limit_order_open(limit_buy_order_usdt_open, limit_sell_order_usdt_open): return { diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index a43e82b22..aa7eefd27 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -30,49 +30,6 @@ from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe, tests_start_time = arrow.get(2018, 10, 3) timeframe_in_minute = 60 -# Helpers for this test file - - -def _validate_ohlc(buy_ohlc_sell_matrice): - for index, ohlc in enumerate(buy_ohlc_sell_matrice): - # if not high < open < low or not high < close < low - if not ohlc[3] >= ohlc[2] >= ohlc[4] or not ohlc[3] >= ohlc[5] >= ohlc[4]: - raise Exception('Line ' + str(index + 1) + ' of ohlc has invalid values!') - return True - - -def _build_dataframe(buy_ohlc_sell_matrice): - _validate_ohlc(buy_ohlc_sell_matrice) - data = [] - for ohlc in buy_ohlc_sell_matrice: - d = { - 'date': tests_start_time.shift( - minutes=( - ohlc[0] * - timeframe_in_minute)).int_timestamp * - 1000, - 'buy': ohlc[1], - 'open': ohlc[2], - 'high': ohlc[3], - 'low': ohlc[4], - 'close': ohlc[5], - 'sell': ohlc[6]} - data.append(d) - - frame = DataFrame(data) - frame['date'] = to_datetime(frame['date'], - unit='ms', - utc=True, - infer_datetime_format=True) - - return frame - - -def _time_on_candle(number): - return np.datetime64(tests_start_time.shift( - minutes=(number * timeframe_in_minute)).int_timestamp * 1000, 'ms') - - # End helper functions # Open trade should be removed from the end tc0 = BTContainer(data=[ From 0807d3106fea5c4d4f54050f64b8f808f6d4501c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 15:34:40 +0200 Subject: [PATCH 142/254] Remove unused import --- tests/edge/test_edge.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py index aa7eefd27..b30d6f998 100644 --- a/tests/edge/test_edge.py +++ b/tests/edge/test_edge.py @@ -8,7 +8,7 @@ from unittest.mock import MagicMock import arrow import numpy as np import pytest -from pandas import DataFrame, to_datetime +from pandas import DataFrame from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.edge import Edge, PairInfo From 2d07cbce597ed3e3053a3d31db0f8f87d35d4c68 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 17:05:41 +0200 Subject: [PATCH 143/254] Fix bad pre-commit installation closes #6713 --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index 5cde1a589..e0b010387 100755 --- a/setup.sh +++ b/setup.sh @@ -90,7 +90,7 @@ function updateenv() { echo "pip install completed" echo if [[ $dev =~ ^[Yy]$ ]]; then - ${PYTHON} -m pre-commit install + ${PYTHON} -m pre_commit install if [ $? -ne 0 ]; then echo "Failed installing pre-commit" exit 1 From 8cac0a47cca89bea4b69de00cbf54505679dcd24 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 17:08:34 +0200 Subject: [PATCH 144/254] Fix joblib being in wrong requirements --- environment.yml | 2 +- freqtrade/misc.py | 2 +- requirements-hyperopt.txt | 1 - requirements.txt | 1 + 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/environment.yml b/environment.yml index f2f961894..19f3c7f5a 100644 --- a/environment.yml +++ b/environment.yml @@ -32,6 +32,7 @@ dependencies: - prompt-toolkit - schedule - python-dateutil + - joblib # ============================ @@ -54,7 +55,6 @@ dependencies: - scikit-learn - filelock - scikit-optimize - - joblib - progressbar2 # ============================ # 4/4 req plot diff --git a/freqtrade/misc.py b/freqtrade/misc.py index be12d8224..55a533725 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -12,7 +12,6 @@ from typing import Any, Iterator, List, Union from typing.io import IO from urllib.parse import urlparse -import joblib import rapidjson from freqtrade.constants import DECIMAL_PER_COIN_FALLBACK, DECIMALS_PER_COIN @@ -94,6 +93,7 @@ def file_dump_joblib(filename: Path, data: Any, log: bool = True) -> None: :param data: Object data to save :return: """ + import joblib if log: logger.info(f'dumping joblib to "{filename}"') diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index aeb7be035..32fc3f4b9 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -6,5 +6,4 @@ scipy==1.8.0 scikit-learn==1.0.2 scikit-optimize==0.9.0 filelock==3.6.0 -joblib==1.1.0 progressbar2==4.0.0 diff --git a/requirements.txt b/requirements.txt index 24f267b67..571d1892c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,6 +20,7 @@ pycoingecko==2.2.0 jinja2==3.1.1 tables==3.7.0 blosc==1.10.6 +joblib==1.1.0 # find first, C search in arrays py_find_1st==1.1.5 From 0f943c482b11a8cd7490fd7da62027d6652e1312 Mon Sep 17 00:00:00 2001 From: Nicolas Papp Date: Sat, 23 Apr 2022 13:15:14 -0300 Subject: [PATCH 145/254] PEP8 code compliance --- freqtrade/data/btanalysis.py | 26 ++++++++++++++----- .../hyperopt_loss_max_drawdown_relative.py | 10 ++++--- freqtrade/optimize/optimize_reports.py | 2 +- freqtrade/plot/plotting.py | 15 ++++++++--- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 6858deb69..3803beb70 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -435,7 +435,7 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str, def _calc_drawdown_series(profit_results: pd.DataFrame, *, date_col: str, value_col: str, - starting_balance : Optional[float] = 0.0) -> pd.DataFrame: + starting_balance: Optional[float] = 0.0) -> pd.DataFrame: max_drawdown_df = pd.DataFrame() max_drawdown_df['cumulative'] = profit_results[value_col].cumsum() max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax() @@ -446,13 +446,15 @@ def _calc_drawdown_series(profit_results: pd.DataFrame, *, date_col: str, value_ max_balance = starting_balance + max_drawdown_df['high_value'] max_drawdown_df['drawdown_relative'] = ((max_balance - cumulative_balance) / max_balance) else: - # This is not completely accurate, - max_drawdown_df['drawdown_relative'] = ((max_drawdown_df['high_value'] - max_drawdown_df['cumulative']) / max_drawdown_df['high_value']) + # This is not completely accurate + max_drawdown_df['drawdown_relative'] = ( + (max_drawdown_df['high_value'] - max_drawdown_df['cumulative']) + / max_drawdown_df['high_value']) return max_drawdown_df def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date', - value_col: str = 'profit_ratio', starting_balance : Optional[float] = 0.0 + value_col: str = 'profit_ratio', starting_balance: Optional[float] = 0.0 ): """ Calculate max drawdown and the corresponding close dates @@ -466,7 +468,11 @@ def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date', if len(trades) == 0: raise ValueError("Trade dataframe empty.") profit_results = trades.sort_values(date_col).reset_index(drop=True) - max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col, starting_balance=starting_balance) + max_drawdown_df = _calc_drawdown_series( + profit_results, + date_col=date_col, + value_col=value_col, + starting_balance=starting_balance) return max_drawdown_df @@ -489,9 +495,15 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date' if len(trades) == 0: raise ValueError("Trade dataframe empty.") profit_results = trades.sort_values(date_col).reset_index(drop=True) - max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col, starting_balance=starting_balance) + max_drawdown_df = _calc_drawdown_series( + profit_results, + date_col=date_col, + value_col=value_col, + starting_balance=starting_balance + ) - idxmin = max_drawdown_df['drawdown_relative'].idxmax() if relative else max_drawdown_df['drawdown'].idxmin() + idxmin = max_drawdown_df['drawdown_relative'].idxmax() if relative \ + else max_drawdown_df['drawdown'].idxmin() if idxmin == 0: raise ValueError("No losing trade, therefore no drawdown.") high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(), date_col] diff --git a/freqtrade/optimize/hyperopt_loss_max_drawdown_relative.py b/freqtrade/optimize/hyperopt_loss_max_drawdown_relative.py index c4dd843b8..62fe76ee6 100644 --- a/freqtrade/optimize/hyperopt_loss_max_drawdown_relative.py +++ b/freqtrade/optimize/hyperopt_loss_max_drawdown_relative.py @@ -4,12 +4,11 @@ MaxDrawDownRelativeHyperOptLoss This module defines the alternative HyperOptLoss class which can be used for Hyperoptimization. """ -from datetime import datetime from typing import Dict from pandas import DataFrame -from freqtrade.data.btanalysis import calculate_underwater, calculate_max_drawdown +from freqtrade.data.btanalysis import calculate_underwater from freqtrade.optimize.hyperopt import IHyperOptLoss @@ -34,7 +33,11 @@ class MaxDrawDownRelativeHyperOptLoss(IHyperOptLoss): """ total_profit = results['profit_abs'].sum() try: - drawdown_df = calculate_underwater(results, value_col='profit_abs', starting_balance=config['available_capital']) + drawdown_df = calculate_underwater( + results, + value_col='profit_abs', + starting_balance=config['available_capital'] + ) max_drawdown = abs(min(drawdown_df['drawdown'])) relative_drawdown = max(drawdown_df['drawdown_relative']) if max_drawdown == 0: @@ -42,4 +45,3 @@ class MaxDrawDownRelativeHyperOptLoss(IHyperOptLoss): return -total_profit / max_drawdown / relative_drawdown except (Exception, ValueError): return -total_profit - diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 2d6e17468..32d16a235 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -769,7 +769,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: if 'max_drawdown_account' in strat_results else ( 'Drawdown', f"{strat_results['max_drawdown']:.2%}"), ('Absolute Drawdown', round_coin_value(strat_results['max_drawdown_abs'], - strat_results['stake_currency'])), + strat_results['stake_currency'])), ('Drawdown high', round_coin_value(strat_results['max_drawdown_high'], strat_results['stake_currency'])), ('Drawdown low', round_coin_value(strat_results['max_drawdown_low'], diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 3d651c1d9..ed403e09f 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -164,7 +164,10 @@ def add_max_drawdown(fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame, Add scatter points indicating max drawdown """ try: - _, highdate, lowdate, _, _, max_drawdown = calculate_max_drawdown(trades, starting_balance=starting_balance) + _, highdate, lowdate, _, _, max_drawdown = calculate_max_drawdown( + trades, + starting_balance=starting_balance + ) drawdown = go.Scatter( x=[highdate, lowdate], @@ -194,7 +197,11 @@ def add_underwater(fig, row, trades: pd.DataFrame, starting_balance: number) -> Add underwater plots """ try: - underwater = calculate_underwater(trades, value_col="profit_abs", starting_balance=starting_balance) + underwater = calculate_underwater( + trades, + value_col="profit_abs", + starting_balance=starting_balance + ) underwater_plot = go.Scatter( x=underwater['date'], @@ -213,9 +220,9 @@ def add_underwater(fig, row, trades: pd.DataFrame, starting_balance: number) -> fillcolor='green', line={'color': 'green'} ) - + fig.add_trace(underwater_plot, row, 1) - fig.add_trace(underwater_plot_relative, row+1, 1) + fig.add_trace(underwater_plot_relative, row + 1, 1) except ValueError: logger.warning("No trades found - not plotting underwater plot") return fig From acec5640143671600878c769d8d7dacc017c5434 Mon Sep 17 00:00:00 2001 From: froggleston Date: Sat, 23 Apr 2022 17:18:38 +0100 Subject: [PATCH 146/254] Update advanced backtesting docs to match fixed buy_reasons script --- docs/advanced-backtesting.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/advanced-backtesting.md b/docs/advanced-backtesting.md index 8a8c1af77..2a484da69 100644 --- a/docs/advanced-backtesting.md +++ b/docs/advanced-backtesting.md @@ -17,15 +17,15 @@ signals **and** trades: freqtrade backtesting -c --timeframe --strategy --timerange= --export=signals ``` -To analyze the buy tags, we need to use the `buy_reasons.py` script from -[froggleston's repo](https://github.com/froggleston/freqtrade-buyreasons). Follow the instructions -in their README to copy the script into your `freqtrade/scripts/` folder. - This will tell freqtrade to output a pickled dictionary of strategy, pairs and corresponding DataFrame of the candles that resulted in buy signals. Depending on how many buys your strategy makes, this file may get quite large, so periodically check your `user_data/backtest_results` folder to delete old exports. +To analyze the buy tags, we need to use the `buy_reasons.py` script from +[froggleston's repo](https://github.com/froggleston/freqtrade-buyreasons). Follow the instructions +in their README to copy the script into your `freqtrade/scripts/` folder. + Before running your next backtest, make sure you either delete your old backtest results or run backtesting with the `--cache none` option to make sure no cached results are used. @@ -47,14 +47,14 @@ running with the `-h` option. To show only certain buy and sell tags in the displayed output, use the following two options: ``` ---buy_reason_list : Comma separated list of buy signals to analyse. Default: "all" ---sell_reason_list : Comma separated list of sell signals to analyse. Default: "stop_loss,trailing_stop_loss" +--enter_reason_list : Comma separated list of enter signals to analyse. Default: "all" +--exit_reason_list : Comma separated list of exit signals to analyse. Default: "stop_loss,trailing_stop_loss" ``` For example: ```bash -python3 scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 --buy_reason_list "buy_tag_a,buy_tag_b" --sell_reason_list "roi,custom_sell_tag_a,stop_loss" +python3 scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 --enter_reason_list "enter_tag_a,enter_tag_b" --exit_reason_list "roi,custom_exit_tag_a,stop_loss" ``` ### Outputting signal candle indicators @@ -65,7 +65,7 @@ indicators. To print out a column for a given set of indicators, use the `--indi option: ```bash -python3 scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 --buy_reason_list "buy_tag_a,buy_tag_b" --sell_reason_list "roi,custom_sell_tag_a,stop_loss" --indicator_list "rsi,rsi_1h,bb_lowerband,ema_9,macd,macdsignal" +python3 scripts/buy_reasons.py -c -s -t -g0,1,2,3,4 --enter_reason_list "enter_tag_a,enter_tag_b" --exit_reason_list "roi,custom_exit_tag_a,stop_loss" --indicator_list "rsi,rsi_1h,bb_lowerband,ema_9,macd,macdsignal" ``` The indicators have to be present in your strategy's main DataFrame (either for your main From 3c17409bd71ab19823db3416a783d894b70886a3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Apr 2022 14:28:15 +0200 Subject: [PATCH 147/254] Update buy to entry in backtesting --- freqtrade/optimize/backtesting.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index f5571c4e2..16f94e083 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -332,11 +332,11 @@ class Backtesting: self.dataprovider._set_cached_df( pair, self.timeframe, df_analyzed, self.config['candle_type_def']) - # Create a copy of the dataframe before shifting, that way the buy signal/tag + # Create a copy of the dataframe before shifting, that way the entry signal/tag # remains on the correct candle for callbacks. df_analyzed = df_analyzed.copy() - # To avoid using data from future, we use buy/sell signals shifted + # To avoid using data from future, we use entry/exit signals shifted # from the previous candle for col in headers[5:]: tag_col = col in ('enter_tag', 'exit_tag') @@ -649,7 +649,7 @@ class Backtesting: proposed_rate=propose_rate, entry_tag=entry_tag, side=direction, ) # default value is the open rate - # We can't place orders higher than current high (otherwise it'd be a stop limit buy) + # We can't place orders higher than current high (otherwise it'd be a stop limit entry) # which freqtrade does not support in live. if direction == "short": propose_rate = max(propose_rate, row[LOW_IDX]) @@ -813,7 +813,7 @@ class Backtesting: if len(open_trades[pair]) > 0: for trade in open_trades[pair]: if trade.open_order_id and trade.nr_of_successful_entries == 0: - # Ignore trade if buy-order did not fill yet + # Ignore trade if entry-order did not fill yet continue sell_row = data[pair][-1] @@ -869,7 +869,7 @@ class Backtesting: # Remove trade due to entry timeout expiration. return True else: - # Close additional buy order + # Close additional entry order del trade.orders[trade.orders.index(order)] if order.side == trade.exit_side: self.timedout_exit_orders += 1 @@ -882,7 +882,7 @@ class Backtesting: self, data: Dict, pair: str, row_index: int, current_time: datetime) -> Optional[Tuple]: try: # Row is treated as "current incomplete candle". - # Buy / sell signals are shifted by 1 to compensate for this. + # entry / exit signals are shifted by 1 to compensate for this. row = data[pair][row_index] except IndexError: # missing Data for one pair at the end. @@ -947,14 +947,14 @@ class Backtesting: self.dataprovider._set_dataframe_max_index(row_index) for t in list(open_trades[pair]): - # 1. Cancel expired buy/sell orders. + # 1. Cancel expired entry/exit orders. if self.check_order_cancel(t, current_time): - # Close trade due to buy timeout expiration. + # Close trade due to entry timeout expiration. open_trade_count -= 1 open_trades[pair].remove(t) self.wallets.update() - # 2. Process buys. + # 2. Process entries. # without positionstacking, we can only have one open trade per pair. # max_open_trades must be respected # don't open on the last row @@ -970,7 +970,7 @@ class Backtesting: if trade: # TODO: hacky workaround to avoid opening > max_open_trades # This emulates previous behavior - not sure if this is correct - # Prevents buying if the trade-slot was freed in this candle + # Prevents entering if the trade-slot was freed in this candle open_trade_count_start += 1 open_trade_count += 1 # logger.debug(f"{pair} - Emulate creation of new trade: {trade}.") @@ -1052,7 +1052,7 @@ class Backtesting: "No data left after adjusting for startup candles.") # Use preprocessed_tmp for date generation (the trimmed dataframe). - # Backtesting will re-trim the dataframes after buy/sell signal generation. + # Backtesting will re-trim the dataframes after entry/exit signal generation. min_date, max_date = history.get_timerange(preprocessed_tmp) logger.info(f'Backtesting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} ' f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} ' From 25c6c5e326a801556f4364d0dc5da95d6772b7af Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Apr 2022 14:30:50 +0200 Subject: [PATCH 148/254] Update backtest sell terminology to exit --- freqtrade/optimize/backtesting.py | 92 +++++++++++++++--------------- tests/optimize/test_backtesting.py | 6 +- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 16f94e083..5442e425b 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -178,7 +178,7 @@ class Backtesting: # Attach Wallets to Strategy baseclass strategy.wallets = self.wallets # Set stoploss_on_exchange to false for backtesting, - # since a "perfect" stoploss-sell is assumed anyway + # since a "perfect" stoploss-exit is assumed anyway # And the regular "stoploss" function would not apply to that case self.strategy.order_types['stoploss_on_exchange'] = False @@ -353,24 +353,24 @@ class Backtesting: data[pair] = df_analyzed[headers].values.tolist() if not df_analyzed.empty else [] return data - def _get_close_rate(self, row: Tuple, trade: LocalTrade, sell: ExitCheckTuple, + def _get_close_rate(self, row: Tuple, trade: LocalTrade, exit: ExitCheckTuple, trade_dur: int) -> float: """ Get close rate for backtesting result """ # Special handling if high or low hit STOP_LOSS or ROI - if sell.exit_type in (ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS): - return self._get_close_rate_for_stoploss(row, trade, sell, trade_dur) - elif sell.exit_type == (ExitType.ROI): - return self._get_close_rate_for_roi(row, trade, sell, trade_dur) + if exit.exit_type in (ExitType.STOP_LOSS, ExitType.TRAILING_STOP_LOSS): + return self._get_close_rate_for_stoploss(row, trade, exit, trade_dur) + elif exit.exit_type == (ExitType.ROI): + return self._get_close_rate_for_roi(row, trade, exit, trade_dur) else: return row[OPEN_IDX] - def _get_close_rate_for_stoploss(self, row: Tuple, trade: LocalTrade, sell: ExitCheckTuple, + def _get_close_rate_for_stoploss(self, row: Tuple, trade: LocalTrade, exit: ExitCheckTuple, trade_dur: int) -> float: # our stoploss was already lower than candle high, # possibly due to a cancelled trade exit. - # sell at open price. + # exit at open price. is_short = trade.is_short or False leverage = trade.leverage or 1.0 side_1 = -1 if is_short else 1 @@ -384,7 +384,7 @@ class Backtesting: # Special case: trailing triggers within same candle as trade opened. Assume most # pessimistic price movement, which is moving just enough to arm stoploss and # immediately going down to stop price. - if sell.exit_type == ExitType.TRAILING_STOP_LOSS and trade_dur == 0: + if exit.exit_type == ExitType.TRAILING_STOP_LOSS and trade_dur == 0: if ( not self.strategy.use_custom_stoploss and self.strategy.trailing_stop and self.strategy.trailing_only_offset_is_reached @@ -403,7 +403,7 @@ class Backtesting: else: assert stop_rate < row[HIGH_IDX] - # Limit lower-end to candle low to avoid sells below the low. + # Limit lower-end to candle low to avoid exits below the low. # This still remains "worst case" - but "worst realistic case". if is_short: return min(row[HIGH_IDX], stop_rate) @@ -413,7 +413,7 @@ class Backtesting: # Set close_rate to stoploss return trade.stop_loss - def _get_close_rate_for_roi(self, row: Tuple, trade: LocalTrade, sell: ExitCheckTuple, + def _get_close_rate_for_roi(self, row: Tuple, trade: LocalTrade, exit: ExitCheckTuple, trade_dur: int) -> float: is_short = trade.is_short or False leverage = trade.leverage or 1.0 @@ -438,7 +438,7 @@ class Backtesting: and roi_entry % self.timeframe_min == 0 and is_new_roi): # new ROI entry came into effect. - # use Open rate if open_rate > calculated sell rate + # use Open rate if open_rate > calculated exit rate return row[OPEN_IDX] if (trade_dur == 0 and ( @@ -461,11 +461,11 @@ class Backtesting: # ROI on opening candles with custom pricing can only # trigger if the entry was at Open or lower wick. # details: https: // github.com/freqtrade/freqtrade/issues/6261 - # If open_rate is < open, only allow sells below the close on red candles. + # If open_rate is < open, only allow exits below the close on red candles. raise ValueError("Opening candle ROI on red candles.") # Use the maximum between close_rate and low as we - # cannot sell outside of a candle. + # cannot exit outside of a candle. # Applies when a new ROI setting comes in place and the whole candle is above that. return min(max(close_rate, row[LOW_IDX]), row[HIGH_IDX]) @@ -500,7 +500,7 @@ class Backtesting: """ Rate is within candle, therefore filled""" return row[LOW_IDX] <= rate <= row[HIGH_IDX] - def _get_sell_trade_entry_for_candle(self, trade: LocalTrade, + def _get_exit_trade_entry_for_candle(self, trade: LocalTrade, row: Tuple) -> Optional[LocalTrade]: # Check if we need to adjust our current positions @@ -512,33 +512,33 @@ class Backtesting: if check_adjust_entry: trade = self._get_adjust_trade_entry_for_candle(trade, row) - sell_candle_time: datetime = row[DATE_IDX].to_pydatetime() + exit_candle_time: datetime = row[DATE_IDX].to_pydatetime() enter = row[SHORT_IDX] if trade.is_short else row[LONG_IDX] exit_ = row[ESHORT_IDX] if trade.is_short else row[ELONG_IDX] - sell = self.strategy.should_exit( - trade, row[OPEN_IDX], sell_candle_time, # type: ignore + exit_ = self.strategy.should_exit( + trade, row[OPEN_IDX], exit_candle_time, # type: ignore enter=enter, exit_=exit_, low=row[LOW_IDX], high=row[HIGH_IDX] ) - if sell.exit_flag: - trade.close_date = sell_candle_time + if exit_.exit_flag: + trade.close_date = exit_candle_time trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60) try: - closerate = self._get_close_rate(row, trade, sell, trade_dur) + closerate = self._get_close_rate(row, trade, exit_, trade_dur) except ValueError: return None # call the custom exit price,with default value as previous closerate current_profit = trade.calc_profit_ratio(closerate) order_type = self.strategy.order_types['exit'] - if sell.exit_type in (ExitType.EXIT_SIGNAL, ExitType.CUSTOM_EXIT): - # Custom exit pricing only for sell-signals + if exit_.exit_type in (ExitType.EXIT_SIGNAL, ExitType.CUSTOM_EXIT): + # Custom exit pricing only for exit-signals if order_type == 'limit': closerate = strategy_safe_wrapper(self.strategy.custom_exit_price, default_retval=closerate)( pair=trade.pair, trade=trade, - current_time=sell_candle_time, + current_time=exit_candle_time, proposed_rate=closerate, current_profit=current_profit) # We can't place orders lower than current low. # freqtrade does not support this in live, and the order would fill immediately @@ -553,12 +553,12 @@ class Backtesting: pair=trade.pair, trade=trade, order_type='limit', amount=trade.amount, rate=closerate, time_in_force=time_in_force, - sell_reason=sell.exit_reason, # deprecated - exit_reason=sell.exit_reason, - current_time=sell_candle_time): + sell_reason=exit_.exit_reason, # deprecated + exit_reason=exit_.exit_reason, + current_time=exit_candle_time): return None - trade.exit_reason = sell.exit_reason + trade.exit_reason = exit_.exit_reason # Checks and adds an exit tag, after checking that the length of the # row has the length for an exit tag column @@ -573,8 +573,8 @@ class Backtesting: order = Order( id=self.order_id_counter, ft_trade_id=trade.id, - order_date=sell_candle_time, - order_update_date=sell_candle_time, + order_date=exit_candle_time, + order_update_date=exit_candle_time, ft_is_open=True, ft_pair=trade.pair, order_id=str(self.order_id_counter), @@ -595,8 +595,8 @@ class Backtesting: return None - def _get_sell_trade_entry(self, trade: LocalTrade, row: Tuple) -> Optional[LocalTrade]: - sell_candle_time: datetime = row[DATE_IDX].to_pydatetime() + def _get_exit_trade_entry(self, trade: LocalTrade, row: Tuple) -> Optional[LocalTrade]: + exit_candle_time: datetime = row[DATE_IDX].to_pydatetime() if self.trading_mode == TradingMode.FUTURES: trade.funding_fees = self.exchange.calculate_funding_fees( @@ -604,20 +604,20 @@ class Backtesting: amount=trade.amount, is_short=trade.is_short, open_date=trade.open_date_utc, - close_date=sell_candle_time, + close_date=exit_candle_time, ) if self.timeframe_detail and trade.pair in self.detail_data: - sell_candle_end = sell_candle_time + timedelta(minutes=self.timeframe_min) + exit_candle_end = exit_candle_time + timedelta(minutes=self.timeframe_min) detail_data = self.detail_data[trade.pair] detail_data = detail_data.loc[ - (detail_data['date'] >= sell_candle_time) & - (detail_data['date'] < sell_candle_end) + (detail_data['date'] >= exit_candle_time) & + (detail_data['date'] < exit_candle_end) ].copy() if len(detail_data) == 0: # Fall back to "regular" data if no detail data was found for this candle - return self._get_sell_trade_entry_for_candle(trade, row) + return self._get_exit_trade_entry_for_candle(trade, row) detail_data.loc[:, 'enter_long'] = row[LONG_IDX] detail_data.loc[:, 'exit_long'] = row[ELONG_IDX] detail_data.loc[:, 'enter_short'] = row[SHORT_IDX] @@ -627,14 +627,14 @@ class Backtesting: headers = ['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long', 'enter_short', 'exit_short', 'enter_tag', 'exit_tag'] for det_row in detail_data[headers].values.tolist(): - res = self._get_sell_trade_entry_for_candle(trade, det_row) + res = self._get_exit_trade_entry_for_candle(trade, det_row) if res: return res return None else: - return self._get_sell_trade_entry_for_candle(trade, row) + return self._get_exit_trade_entry_for_candle(trade, row) def get_valid_price_and_stake( self, pair: str, row: Tuple, propose_rate: float, stake_amount: Optional[float], @@ -815,11 +815,11 @@ class Backtesting: if trade.open_order_id and trade.nr_of_successful_entries == 0: # Ignore trade if entry-order did not fill yet continue - sell_row = data[pair][-1] + exit_row = data[pair][-1] - trade.close_date = sell_row[DATE_IDX].to_pydatetime() + trade.close_date = exit_row[DATE_IDX].to_pydatetime() trade.exit_reason = ExitType.FORCE_EXIT.value - trade.close(sell_row[OPEN_IDX], show_msg=False) + trade.close(exit_row[OPEN_IDX], show_msg=False) LocalTrade.close_bt_trade(trade) # Deepcopy object to have wallets update correctly trade1 = deepcopy(trade) @@ -985,18 +985,18 @@ class Backtesting: LocalTrade.add_bt_trade(trade) self.wallets.update() - # 4. Create sell orders (if any) + # 4. Create exit orders (if any) if not trade.open_order_id: - self._get_sell_trade_entry(trade, row) # Place sell order if necessary + self._get_exit_trade_entry(trade, row) # Place exit order if necessary - # 5. Process sell orders. + # 5. Process exit orders. order = trade.select_order(trade.exit_side, is_open=True) if order and self._get_order_filled(order.price, row): trade.open_order_id = None trade.close_date = current_time trade.close(order.price, show_msg=False) - # logger.debug(f"{pair} - Backtesting sell {trade}") + # logger.debug(f"{pair} - Backtesting exit {trade}") open_trade_count -= 1 open_trades[pair].remove(trade) LocalTrade.close_bt_trade(trade) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 797d3bafa..4d32a7516 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -714,7 +714,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None: ) # No data available. - res = backtesting._get_sell_trade_entry(trade, row_sell) + res = backtesting._get_exit_trade_entry(trade, row_sell) assert res is not None assert res.exit_reason == ExitType.ROI.value assert res.close_date_utc == datetime(2020, 1, 1, 5, 0, tzinfo=timezone.utc) @@ -727,13 +727,13 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None: [], columns=['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long', 'enter_short', 'exit_short', 'long_tag', 'short_tag', 'exit_tag']) - res = backtesting._get_sell_trade_entry(trade, row) + res = backtesting._get_exit_trade_entry(trade, row) assert res is None # Assign backtest-detail data backtesting.detail_data[pair] = row_detail - res = backtesting._get_sell_trade_entry(trade, row_sell) + res = backtesting._get_exit_trade_entry(trade, row_sell) assert res is not None assert res.exit_reason == ExitType.ROI.value # Sell at minute 3 (not available above!) From 6ff3b178b0916ac726f65d3ad2d5929c44b5292f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 15:26:58 +0200 Subject: [PATCH 149/254] Add direction column to pairlocks --- freqtrade/persistence/migrations.py | 57 ++++++++++++++++++++++++----- freqtrade/persistence/models.py | 2 + tests/test_persistence.py | 47 ++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 9 deletions(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index f020f990c..eff2d69f3 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -9,7 +9,7 @@ from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) -def get_table_names_for_table(inspector, tabletype): +def get_table_names_for_table(inspector, tabletype) -> List[str]: return [t for t in inspector.get_table_names() if t.startswith(tabletype)] @@ -21,7 +21,7 @@ def get_column_def(columns: List, column: str, default: str) -> str: return default if not has_column(columns, column) else column -def get_backup_name(tabs, backup_prefix: str): +def get_backup_name(tabs: List[str], backup_prefix: str): table_back_name = backup_prefix for i, table_back_name in enumerate(tabs): table_back_name = f'{backup_prefix}{i}' @@ -56,6 +56,16 @@ def set_sequence_ids(engine, order_id, trade_id): connection.execute(text(f"ALTER SEQUENCE trades_id_seq RESTART WITH {trade_id}")) +def drop_index_on_table(engine, inspector, table_bak_name): + with engine.begin() as connection: + # drop indexes on backup table in new session + for index in inspector.get_indexes(table_bak_name): + if engine.name == 'mysql': + connection.execute(text(f"drop index {index['name']} on {table_bak_name}")) + else: + connection.execute(text(f"drop index {index['name']}")) + + def migrate_trades_and_orders_table( decl_base, inspector, engine, trade_back_name: str, cols: List, @@ -116,13 +126,7 @@ def migrate_trades_and_orders_table( with engine.begin() as connection: connection.execute(text(f"alter table trades rename to {trade_back_name}")) - with engine.begin() as connection: - # drop indexes on backup table in new session - for index in inspector.get_indexes(trade_back_name): - if engine.name == 'mysql': - connection.execute(text(f"drop index {index['name']} on {trade_back_name}")) - else: - connection.execute(text(f"drop index {index['name']}")) + drop_index_on_table(engine, inspector, trade_back_name) order_id, trade_id = get_last_sequence_ids(engine, trade_back_name, order_back_name) @@ -205,6 +209,31 @@ def migrate_orders_table(engine, table_back_name: str, cols_order: List): """)) +def migrate_pairlocks_table( + decl_base, inspector, engine, + pairlock_back_name: str, cols: List): + + # Schema migration necessary + with engine.begin() as connection: + connection.execute(text(f"alter table pairlocks rename to {pairlock_back_name}")) + + drop_index_on_table(engine, inspector, pairlock_back_name) + + direction = get_column_def(cols, 'direction', "'*'") + + # let SQLAlchemy create the schema as required + decl_base.metadata.create_all(engine) + # Copy data back - following the correct schema + with engine.begin() as connection: + connection.execute(text(f"""insert into pairlocks + (id, pair, direction, reason, lock_time, + lock_end_time, active) + select id, pair, {direction} direction, reason, lock_time, + lock_end_time, active + from {pairlock_back_name} + """)) + + def set_sqlite_to_wal(engine): if engine.name == 'sqlite' and str(engine.url) != 'sqlite://': # Set Mode to @@ -220,10 +249,13 @@ def check_migrate(engine, decl_base, previous_tables) -> None: cols_trades = inspector.get_columns('trades') cols_orders = inspector.get_columns('orders') + cols_pairlocks = inspector.get_columns('pairlocks') tabs = get_table_names_for_table(inspector, 'trades') table_back_name = get_backup_name(tabs, 'trades_bak') order_tabs = get_table_names_for_table(inspector, 'orders') order_table_bak_name = get_backup_name(order_tabs, 'orders_bak') + pairlock_tabs = get_table_names_for_table(inspector, 'pairlocks') + pairlock_table_bak_name = get_backup_name(pairlock_tabs, 'pairlocks_bak') # Check if migration necessary # Migrates both trades and orders table! @@ -236,6 +268,13 @@ def check_migrate(engine, decl_base, previous_tables) -> None: decl_base, inspector, engine, table_back_name, cols_trades, order_table_bak_name, cols_orders) + if not has_column(cols_pairlocks, 'direction'): + logger.info(f"Running database migration for pairlocks - " + f"backup: {pairlock_table_bak_name}") + + migrate_pairlocks_table( + decl_base, inspector, engine, pairlock_table_bak_name, cols_pairlocks + ) if 'orders' not in previous_tables and 'trades' in previous_tables: raise OperationalException( "Your database seems to be very old. " diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index a9c07f12c..4aa1c6a4d 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -1428,6 +1428,8 @@ class PairLock(_DECL_BASE): id = Column(Integer, primary_key=True) pair = Column(String(25), nullable=False, index=True) + # lock direction - long, short or * (for both) + direction = Column(String(25), nullable=False, default="*") reason = Column(String(255), nullable=True) # Time the pair was locked (start time) lock_time = Column(DateTime, nullable=False) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 801e0e35f..58d3a4de4 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -15,6 +15,7 @@ from freqtrade.enums import TradingMode from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.persistence import LocalTrade, Order, Trade, clean_dry_run_db, init_db from freqtrade.persistence.migrations import get_last_sequence_ids, set_sequence_ids +from freqtrade.persistence.models import PairLock from tests.conftest import create_mock_trades, create_mock_trades_with_leverage, log_has, log_has_re @@ -1427,6 +1428,52 @@ def test_migrate_set_sequence_ids(): assert engine.begin.call_count == 0 +def test_migrate_pairlocks(mocker, default_conf, fee, caplog): + """ + Test Database migration (starting with new pairformat) + """ + caplog.set_level(logging.DEBUG) + # Always create all columns apart from the last! + create_table_old = """CREATE TABLE pairlocks ( + id INTEGER NOT NULL, + pair VARCHAR(25) NOT NULL, + reason VARCHAR(255), + lock_time DATETIME NOT NULL, + lock_end_time DATETIME NOT NULL, + active BOOLEAN NOT NULL, + PRIMARY KEY (id) + ) + """ + create_index1 = "CREATE INDEX ix_pairlocks_pair ON pairlocks (pair)" + create_index2 = "CREATE INDEX ix_pairlocks_lock_end_time ON pairlocks (lock_end_time)" + create_index3 = "CREATE INDEX ix_pairlocks_active ON pairlocks (active)" + insert_table_old = """INSERT INTO pairlocks ( + id, pair, reason, lock_time, lock_end_time, active) + VALUES (1, 'ETH/BTC', 'Auto lock', '2021-07-12 18:41:03', '2021-07-11 18:45:00', 1) + """ + insert_table_old2 = """INSERT INTO pairlocks ( + id, pair, reason, lock_time, lock_end_time, active) + VALUES (2, '*', 'Lock all', '2021-07-12 18:41:03', '2021-07-12 19:00:00', 1) + """ + engine = create_engine('sqlite://') + mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine) + # Create table using the old format + with engine.begin() as connection: + connection.execute(text(create_table_old)) + + connection.execute(text(insert_table_old)) + connection.execute(text(insert_table_old2)) + connection.execute(text(create_index1)) + connection.execute(text(create_index2)) + connection.execute(text(create_index3)) + + init_db(default_conf['db_url'], default_conf['dry_run']) + + assert len(PairLock.query.all()) == 2 + assert len(PairLock.query.filter(PairLock.pair == '*').all()) == 1 + assert len(PairLock.query.filter(PairLock.pair == 'ETH/BTC').all()) == 1 + + def test_adjust_stop_loss(fee): trade = Trade( pair='ADA/USDT', From 9e199165b4957624d13e8fbda9bfc6bdd28d3d83 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 23 Apr 2022 19:58:20 +0200 Subject: [PATCH 150/254] Update protection-interface to support per-side locks --- docs/includes/protections.md | 5 +++- freqtrade/freqtradebot.py | 8 +++---- freqtrade/optimize/backtesting.py | 9 +++---- freqtrade/plugins/protectionmanager.py | 11 +++++---- .../plugins/protections/cooldown_period.py | 12 +++++----- freqtrade/plugins/protections/iprotection.py | 7 +++--- .../plugins/protections/low_profit_pairs.py | 12 +++++----- .../protections/max_drawdown_protection.py | 14 +++++------ .../plugins/protections/stoploss_guard.py | 24 ++++++++++++------- 9 files changed, 58 insertions(+), 44 deletions(-) diff --git a/docs/includes/protections.md b/docs/includes/protections.md index 0757d2f6d..a242a6256 100644 --- a/docs/includes/protections.md +++ b/docs/includes/protections.md @@ -48,6 +48,8 @@ If `trade_limit` or more trades resulted in stoploss, trading will stop for `sto This applies across all pairs, unless `only_per_pair` is set to true, which will then only look at one pair at a time. +Similarly, this protection will by default look at all trades (long and short). For futures bots, setting `only_per_side` will make the bot only one side, and will then only lock this one side. + The below example stops trading for all pairs for 4 candles after the last trade if the bot hit stoploss 4 times within the last 24 candles. ``` python @@ -59,7 +61,8 @@ def protections(self): "lookback_period_candles": 24, "trade_limit": 4, "stop_duration_candles": 4, - "only_per_pair": False + "only_per_pair": False, + "only_per_side": True } ] ``` diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 57d7cac3c..d3408ada2 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1604,21 +1604,21 @@ class FreqtradeBot(LoggingMixin): if not trade.is_open: if send_msg and not stoploss_order and not trade.open_order_id: self._notify_exit(trade, '', True) - self.handle_protections(trade.pair) + self.handle_protections(trade.pair, trade.trade_direction) elif send_msg and not trade.open_order_id: # Enter fill self._notify_enter(trade, order, fill=True) return False - def handle_protections(self, pair: str) -> None: - prot_trig = self.protections.stop_per_pair(pair) + def handle_protections(self, pair: str, side: str) -> None: + prot_trig = self.protections.stop_per_pair(pair, side=side) if prot_trig: msg = {'type': RPCMessageType.PROTECTION_TRIGGER, } msg.update(prot_trig.to_json()) self.rpc.send_msg(msg) - prot_trig_glb = self.protections.global_stop() + prot_trig_glb = self.protections.global_stop(side=side) if prot_trig_glb: msg = {'type': RPCMessageType.PROTECTION_TRIGGER_GLOBAL, } msg.update(prot_trig_glb.to_json()) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 5442e425b..86c52e737 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -849,10 +849,10 @@ class Backtesting: return 'short' return None - def run_protections(self, enable_protections, pair: str, current_time: datetime): + def run_protections(self, enable_protections, pair: str, current_time: datetime, side: str): if enable_protections: - self.protections.stop_per_pair(pair, current_time) - self.protections.global_stop(current_time) + self.protections.stop_per_pair(pair, current_time, side) + self.protections.global_stop(current_time, side) def check_order_cancel(self, trade: LocalTrade, current_time) -> bool: """ @@ -1002,7 +1002,8 @@ class Backtesting: LocalTrade.close_bt_trade(trade) trades.append(trade) self.wallets.update() - self.run_protections(enable_protections, pair, current_time) + self.run_protections( + enable_protections, pair, current_time, trade.trade_direction) # Move time one configured time_interval ahead. self.progress.increment() diff --git a/freqtrade/plugins/protectionmanager.py b/freqtrade/plugins/protectionmanager.py index 2510d6fee..e8c3fa02d 100644 --- a/freqtrade/plugins/protectionmanager.py +++ b/freqtrade/plugins/protectionmanager.py @@ -44,13 +44,14 @@ class ProtectionManager(): """ return [{p.name: p.short_desc()} for p in self._protection_handlers] - def global_stop(self, now: Optional[datetime] = None) -> Optional[PairLock]: + def global_stop(self, now: Optional[datetime] = None, side: str = 'long') -> Optional[PairLock]: if not now: now = datetime.now(timezone.utc) result = None for protection_handler in self._protection_handlers: if protection_handler.has_global_stop: - lock, until, reason = protection_handler.global_stop(now) + lock, until, reason, lock_side = protection_handler.global_stop( + date_now=now, side=side) # Early stopping - first positive result blocks further trades if lock and until: @@ -58,13 +59,15 @@ class ProtectionManager(): result = PairLocks.lock_pair('*', until, reason, now=now) return result - def stop_per_pair(self, pair, now: Optional[datetime] = None) -> Optional[PairLock]: + def stop_per_pair( + self, pair, now: Optional[datetime] = None, side: str = 'long') -> Optional[PairLock]: if not now: now = datetime.now(timezone.utc) result = None for protection_handler in self._protection_handlers: if protection_handler.has_local_stop: - lock, until, reason = protection_handler.stop_per_pair(pair, now) + lock, until, reason, lock_side = protection_handler.stop_per_pair( + pair=pair, date_now=now, side=side) if lock and until: if not PairLocks.is_pair_locked(pair, until): result = PairLocks.lock_pair(pair, until, reason, now=now) diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py index a2d8eca34..a75e4fc67 100644 --- a/freqtrade/plugins/protections/cooldown_period.py +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -26,7 +26,7 @@ class CooldownPeriod(IProtection): """ return (f"{self.name} - Cooldown period of {self.stop_duration_str}.") - def _cooldown_period(self, pair: str, date_now: datetime, ) -> ProtectionReturn: + def _cooldown_period(self, pair: str, date_now: datetime) -> ProtectionReturn: """ Get last trade for this pair """ @@ -45,11 +45,11 @@ class CooldownPeriod(IProtection): self.log_once(f"Cooldown for {pair} for {self.stop_duration_str}.", logger.info) until = self.calculate_lock_end([trade], self._stop_duration) - return True, until, self._reason() + return True, until, self._reason(), None - return False, None, None + return False, None, None, None - def global_stop(self, date_now: datetime) -> ProtectionReturn: + def global_stop(self, date_now: datetime, side: str) -> ProtectionReturn: """ Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". @@ -57,9 +57,9 @@ class CooldownPeriod(IProtection): If true, all pairs will be locked with until """ # Not implemented for cooldown period. - return False, None, None + return False, None, None, None - def stop_per_pair(self, pair: str, date_now: datetime) -> ProtectionReturn: + def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> ProtectionReturn: """ Stops trading (position entering) for this pair This must evaluate to true for the whole period of the "cooldown period". diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index e0a89e334..5f1029eb5 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -12,7 +12,8 @@ from freqtrade.persistence import LocalTrade logger = logging.getLogger(__name__) -ProtectionReturn = Tuple[bool, Optional[datetime], Optional[str]] +# lock, until, reason, lock_side +ProtectionReturn = Tuple[bool, Optional[datetime], Optional[str], Optional[str]] class IProtection(LoggingMixin, ABC): @@ -80,14 +81,14 @@ class IProtection(LoggingMixin, ABC): """ @abstractmethod - def global_stop(self, date_now: datetime) -> ProtectionReturn: + def global_stop(self, date_now: datetime, side: str) -> ProtectionReturn: """ Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". """ @abstractmethod - def stop_per_pair(self, pair: str, date_now: datetime) -> ProtectionReturn: + def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> ProtectionReturn: """ Stops trading (position entering) for this pair This must evaluate to true for the whole period of the "cooldown period". diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py index 7822ce73c..38fd6e734 100644 --- a/freqtrade/plugins/protections/low_profit_pairs.py +++ b/freqtrade/plugins/protections/low_profit_pairs.py @@ -51,7 +51,7 @@ class LowProfitPairs(IProtection): # trades = Trade.get_trades(filters).all() if len(trades) < self._trade_limit: # Not enough trades in the relevant period - return False, None, None + return False, None, None, None profit = sum(trade.close_profit for trade in trades if trade.close_profit) if profit < self._required_profit: @@ -60,20 +60,20 @@ class LowProfitPairs(IProtection): f"within {self._lookback_period} minutes.", logger.info) until = self.calculate_lock_end(trades, self._stop_duration) - return True, until, self._reason(profit) + return True, until, self._reason(profit), None - return False, None, None + return False, None, None, None - def global_stop(self, date_now: datetime) -> ProtectionReturn: + def global_stop(self, date_now: datetime, side: str) -> ProtectionReturn: """ Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". :return: Tuple of [bool, until, reason]. If true, all pairs will be locked with until """ - return False, None, None + return False, None, None, None - def stop_per_pair(self, pair: str, date_now: datetime) -> ProtectionReturn: + def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> ProtectionReturn: """ Stops trading (position entering) for this pair This must evaluate to true for the whole period of the "cooldown period". diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index b6ef92bd5..e6cc2ba79 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -51,14 +51,14 @@ class MaxDrawdown(IProtection): if len(trades) < self._trade_limit: # Not enough trades in the relevant period - return False, None, None + return False, None, None, None # Drawdown is always positive try: # TODO: This should use absolute profit calculation, considering account balance. drawdown, _, _, _, _, _ = calculate_max_drawdown(trades_df, value_col='close_profit') except ValueError: - return False, None, None + return False, None, None, None if drawdown > self._max_allowed_drawdown: self.log_once( @@ -66,11 +66,11 @@ class MaxDrawdown(IProtection): f" within {self.lookback_period_str}.", logger.info) until = self.calculate_lock_end(trades, self._stop_duration) - return True, until, self._reason(drawdown) + return True, until, self._reason(drawdown), None - return False, None, None + return False, None, None, None - def global_stop(self, date_now: datetime) -> ProtectionReturn: + def global_stop(self, date_now: datetime, side: str) -> ProtectionReturn: """ Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". @@ -79,11 +79,11 @@ class MaxDrawdown(IProtection): """ return self._max_drawdown(date_now) - def stop_per_pair(self, pair: str, date_now: datetime) -> ProtectionReturn: + def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> ProtectionReturn: """ Stops trading (position entering) for this pair This must evaluate to true for the whole period of the "cooldown period". :return: Tuple of [bool, until, reason]. If true, this pair will be locked with until """ - return False, None, None + return False, None, None, None diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index 8d7fb2a0e..c8e4dcd21 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -1,7 +1,7 @@ import logging from datetime import datetime, timedelta -from typing import Any, Dict +from typing import Any, Dict, Optional from freqtrade.enums import ExitType from freqtrade.persistence import Trade @@ -21,6 +21,7 @@ class StoplossGuard(IProtection): self._trade_limit = protection_config.get('trade_limit', 10) self._disable_global_stop = protection_config.get('only_per_pair', False) + self._only_per_side = protection_config.get('only_per_side', False) def short_desc(self) -> str: """ @@ -36,7 +37,8 @@ class StoplossGuard(IProtection): return (f'{self._trade_limit} stoplosses in {self._lookback_period} min, ' f'locking for {self._stop_duration} min.') - def _stoploss_guard(self, date_now: datetime, pair: str = None) -> ProtectionReturn: + def _stoploss_guard( + self, date_now: datetime, pair: Optional[str], side: str) -> ProtectionReturn: """ Evaluate recent trades """ @@ -48,15 +50,19 @@ class StoplossGuard(IProtection): ExitType.STOPLOSS_ON_EXCHANGE.value) and trade.close_profit and trade.close_profit < 0)] + if self._only_per_side and side: + # Long or short trades only + trades = [trade for trade in trades if trade.trade_direction == side] + if len(trades) < self._trade_limit: - return False, None, None + return False, None, None, None self.log_once(f"Trading stopped due to {self._trade_limit} " f"stoplosses within {self._lookback_period} minutes.", logger.info) until = self.calculate_lock_end(trades, self._stop_duration) - return True, until, self._reason() + return True, until, self._reason(), (side if self._only_per_side else None) - def global_stop(self, date_now: datetime) -> ProtectionReturn: + def global_stop(self, date_now: datetime, side: str) -> ProtectionReturn: """ Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". @@ -64,14 +70,14 @@ class StoplossGuard(IProtection): If true, all pairs will be locked with until """ if self._disable_global_stop: - return False, None, None - return self._stoploss_guard(date_now, None) + return False, None, None, None + return self._stoploss_guard(date_now, None, side) - def stop_per_pair(self, pair: str, date_now: datetime) -> ProtectionReturn: + def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> ProtectionReturn: """ Stops trading (position entering) for this pair This must evaluate to true for the whole period of the "cooldown period". :return: Tuple of [bool, until, reason]. If true, this pair will be locked with until """ - return self._stoploss_guard(date_now, pair) + return self._stoploss_guard(date_now, pair, side) From b7cada1edd55e50e3017f55f649bb5ae97f98a76 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Apr 2022 10:29:19 +0200 Subject: [PATCH 151/254] Convert ProtectionReturn to dataclass --- docs/developer.md | 3 ++- freqtrade/plugins/protectionmanager.py | 19 +++++++-------- .../plugins/protections/cooldown_period.py | 17 +++++++++----- freqtrade/plugins/protections/iprotection.py | 16 +++++++++---- .../plugins/protections/low_profit_pairs.py | 20 +++++++++------- .../protections/max_drawdown_protection.py | 23 +++++++++++-------- .../plugins/protections/stoploss_guard.py | 17 +++++++++----- tests/plugins/test_protections.py | 4 ++-- tests/test_freqtradebot.py | 5 ++-- 9 files changed, 74 insertions(+), 50 deletions(-) diff --git a/docs/developer.md b/docs/developer.md index 1cc16294b..185bfc92e 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -200,11 +200,12 @@ For that reason, they must implement the following methods: * `global_stop()` * `stop_per_pair()`. -`global_stop()` and `stop_per_pair()` must return a ProtectionReturn tuple, which consists of: +`global_stop()` and `stop_per_pair()` must return a ProtectionReturn object, which consists of: * lock pair - boolean * lock until - datetime - until when should the pair be locked (will be rounded up to the next new candle) * reason - string, used for logging and storage in the database +* lock_side - long, short or '*'. The `until` portion should be calculated using the provided `calculate_lock_end()` method. diff --git a/freqtrade/plugins/protectionmanager.py b/freqtrade/plugins/protectionmanager.py index e8c3fa02d..6a54c4369 100644 --- a/freqtrade/plugins/protectionmanager.py +++ b/freqtrade/plugins/protectionmanager.py @@ -50,13 +50,10 @@ class ProtectionManager(): result = None for protection_handler in self._protection_handlers: if protection_handler.has_global_stop: - lock, until, reason, lock_side = protection_handler.global_stop( - date_now=now, side=side) - - # Early stopping - first positive result blocks further trades - if lock and until: - if not PairLocks.is_global_lock(until): - result = PairLocks.lock_pair('*', until, reason, now=now) + lock = protection_handler.global_stop(date_now=now, side=side) + if lock and lock.until: + if not PairLocks.is_global_lock(lock.until): + result = PairLocks.lock_pair('*', lock.until, lock.reason, now=now) return result def stop_per_pair( @@ -66,9 +63,9 @@ class ProtectionManager(): result = None for protection_handler in self._protection_handlers: if protection_handler.has_local_stop: - lock, until, reason, lock_side = protection_handler.stop_per_pair( + lock = protection_handler.stop_per_pair( pair=pair, date_now=now, side=side) - if lock and until: - if not PairLocks.is_pair_locked(pair, until): - result = PairLocks.lock_pair(pair, until, reason, now=now) + if lock and lock.until: + if not PairLocks.is_pair_locked(pair, lock.until): + result = PairLocks.lock_pair(pair, lock.until, lock.reason, now=now) return result diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py index a75e4fc67..a1d7d4291 100644 --- a/freqtrade/plugins/protections/cooldown_period.py +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -1,6 +1,7 @@ import logging from datetime import datetime, timedelta +from typing import Optional from freqtrade.persistence import Trade from freqtrade.plugins.protections import IProtection, ProtectionReturn @@ -26,7 +27,7 @@ class CooldownPeriod(IProtection): """ return (f"{self.name} - Cooldown period of {self.stop_duration_str}.") - def _cooldown_period(self, pair: str, date_now: datetime) -> ProtectionReturn: + def _cooldown_period(self, pair: str, date_now: datetime) -> Optional[ProtectionReturn]: """ Get last trade for this pair """ @@ -45,11 +46,15 @@ class CooldownPeriod(IProtection): self.log_once(f"Cooldown for {pair} for {self.stop_duration_str}.", logger.info) until = self.calculate_lock_end([trade], self._stop_duration) - return True, until, self._reason(), None + return ProtectionReturn( + lock=True, + until=until, + reason=self._reason(), + ) - return False, None, None, None + return None - def global_stop(self, date_now: datetime, side: str) -> ProtectionReturn: + def global_stop(self, date_now: datetime, side: str) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". @@ -57,9 +62,9 @@ class CooldownPeriod(IProtection): If true, all pairs will be locked with until """ # Not implemented for cooldown period. - return False, None, None, None + return None - def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> ProtectionReturn: + def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for this pair This must evaluate to true for the whole period of the "cooldown period". diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index 5f1029eb5..0eff796b3 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -1,8 +1,9 @@ import logging from abc import ABC, abstractmethod +from dataclasses import dataclass from datetime import datetime, timedelta, timezone -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, List, Optional from freqtrade.exchange import timeframe_to_minutes from freqtrade.misc import plural @@ -12,8 +13,13 @@ from freqtrade.persistence import LocalTrade logger = logging.getLogger(__name__) -# lock, until, reason, lock_side -ProtectionReturn = Tuple[bool, Optional[datetime], Optional[str], Optional[str]] + +@dataclass +class ProtectionReturn: + lock: bool + until: datetime + reason: Optional[str] + lock_side: Optional[str] = None class IProtection(LoggingMixin, ABC): @@ -81,14 +87,14 @@ class IProtection(LoggingMixin, ABC): """ @abstractmethod - def global_stop(self, date_now: datetime, side: str) -> ProtectionReturn: + def global_stop(self, date_now: datetime, side: str) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". """ @abstractmethod - def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> ProtectionReturn: + def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for this pair This must evaluate to true for the whole period of the "cooldown period". diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py index 38fd6e734..a4b09bb66 100644 --- a/freqtrade/plugins/protections/low_profit_pairs.py +++ b/freqtrade/plugins/protections/low_profit_pairs.py @@ -1,7 +1,7 @@ import logging from datetime import datetime, timedelta -from typing import Any, Dict +from typing import Any, Dict, Optional from freqtrade.persistence import Trade from freqtrade.plugins.protections import IProtection, ProtectionReturn @@ -35,7 +35,7 @@ class LowProfitPairs(IProtection): return (f'{profit} < {self._required_profit} in {self.lookback_period_str}, ' f'locking for {self.stop_duration_str}.') - def _low_profit(self, date_now: datetime, pair: str) -> ProtectionReturn: + def _low_profit(self, date_now: datetime, pair: str) -> Optional[ProtectionReturn]: """ Evaluate recent trades for pair """ @@ -51,7 +51,7 @@ class LowProfitPairs(IProtection): # trades = Trade.get_trades(filters).all() if len(trades) < self._trade_limit: # Not enough trades in the relevant period - return False, None, None, None + return None profit = sum(trade.close_profit for trade in trades if trade.close_profit) if profit < self._required_profit: @@ -60,20 +60,24 @@ class LowProfitPairs(IProtection): f"within {self._lookback_period} minutes.", logger.info) until = self.calculate_lock_end(trades, self._stop_duration) - return True, until, self._reason(profit), None + return ProtectionReturn( + lock=True, + until=until, + reason=self._reason(profit), + ) - return False, None, None, None + return None - def global_stop(self, date_now: datetime, side: str) -> ProtectionReturn: + def global_stop(self, date_now: datetime, side: str) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". :return: Tuple of [bool, until, reason]. If true, all pairs will be locked with until """ - return False, None, None, None + return None - def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> ProtectionReturn: + def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for this pair This must evaluate to true for the whole period of the "cooldown period". diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index e6cc2ba79..f489522cf 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -1,7 +1,7 @@ import logging from datetime import datetime, timedelta -from typing import Any, Dict +from typing import Any, Dict, Optional import pandas as pd @@ -39,7 +39,7 @@ class MaxDrawdown(IProtection): return (f'{drawdown} passed {self._max_allowed_drawdown} in {self.lookback_period_str}, ' f'locking for {self.stop_duration_str}.') - def _max_drawdown(self, date_now: datetime) -> ProtectionReturn: + def _max_drawdown(self, date_now: datetime) -> Optional[ProtectionReturn]: """ Evaluate recent trades for drawdown ... """ @@ -51,14 +51,14 @@ class MaxDrawdown(IProtection): if len(trades) < self._trade_limit: # Not enough trades in the relevant period - return False, None, None, None + return None # Drawdown is always positive try: # TODO: This should use absolute profit calculation, considering account balance. drawdown, _, _, _, _, _ = calculate_max_drawdown(trades_df, value_col='close_profit') except ValueError: - return False, None, None, None + return None if drawdown > self._max_allowed_drawdown: self.log_once( @@ -66,11 +66,16 @@ class MaxDrawdown(IProtection): f" within {self.lookback_period_str}.", logger.info) until = self.calculate_lock_end(trades, self._stop_duration) - return True, until, self._reason(drawdown), None + # return True, until, self._reason(drawdown), None + return ProtectionReturn( + lock=True, + until=until, + reason=self._reason(drawdown), + ) - return False, None, None, None + return None - def global_stop(self, date_now: datetime, side: str) -> ProtectionReturn: + def global_stop(self, date_now: datetime, side: str) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". @@ -79,11 +84,11 @@ class MaxDrawdown(IProtection): """ return self._max_drawdown(date_now) - def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> ProtectionReturn: + def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for this pair This must evaluate to true for the whole period of the "cooldown period". :return: Tuple of [bool, until, reason]. If true, this pair will be locked with until """ - return False, None, None, None + return None diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index c8e4dcd21..bb442575e 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -38,7 +38,7 @@ class StoplossGuard(IProtection): f'locking for {self._stop_duration} min.') def _stoploss_guard( - self, date_now: datetime, pair: Optional[str], side: str) -> ProtectionReturn: + self, date_now: datetime, pair: Optional[str], side: str) -> Optional[ProtectionReturn]: """ Evaluate recent trades """ @@ -55,14 +55,19 @@ class StoplossGuard(IProtection): trades = [trade for trade in trades if trade.trade_direction == side] if len(trades) < self._trade_limit: - return False, None, None, None + return None self.log_once(f"Trading stopped due to {self._trade_limit} " f"stoplosses within {self._lookback_period} minutes.", logger.info) until = self.calculate_lock_end(trades, self._stop_duration) - return True, until, self._reason(), (side if self._only_per_side else None) + return ProtectionReturn( + lock=True, + until=until, + reason=self._reason(), + lock_side=(side if self._only_per_side else None) + ) - def global_stop(self, date_now: datetime, side: str) -> ProtectionReturn: + def global_stop(self, date_now: datetime, side: str) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". @@ -70,10 +75,10 @@ class StoplossGuard(IProtection): If true, all pairs will be locked with until """ if self._disable_global_stop: - return False, None, None, None + return None return self._stoploss_guard(date_now, None, side) - def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> ProtectionReturn: + def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for this pair This must evaluate to true for the whole period of the "cooldown period". diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 6b69f5481..c8a3b7a82 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -45,9 +45,9 @@ def test_protectionmanager(mocker, default_conf): for handler in freqtrade.protections._protection_handlers: assert handler.name in constants.AVAILABLE_PROTECTIONS if not handler.has_global_stop: - assert handler.global_stop(datetime.utcnow()) == (False, None, None) + assert handler.global_stop(datetime.utcnow(), '*') is None if not handler.has_local_stop: - assert handler.stop_per_pair('XRP/BTC', datetime.utcnow()) == (False, None, None) + assert handler.stop_per_pair('XRP/BTC', datetime.utcnow(), '*') is None @pytest.mark.parametrize('timeframe,expected,protconf', [ diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 3737c7c05..0ae36f0fd 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -21,6 +21,7 @@ from freqtrade.exceptions import (DependencyException, ExchangeError, Insufficie from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Order, PairLocks, Trade from freqtrade.persistence.models import PairLock +from freqtrade.plugins.protections.iprotection import ProtectionReturn from freqtrade.worker import Worker from tests.conftest import (create_mock_trades, get_patched_freqtradebot, get_patched_worker, log_has, log_has_re, patch_edge, patch_exchange, patch_get_signal, @@ -441,9 +442,9 @@ def test_handle_protections(mocker, default_conf_usdt, fee, is_short): freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade.protections._protection_handlers[1].global_stop = MagicMock( - return_value=(True, arrow.utcnow().shift(hours=1).datetime, "asdf")) + return_value=ProtectionReturn(True, arrow.utcnow().shift(hours=1).datetime, "asdf")) create_mock_trades(fee, is_short) - freqtrade.handle_protections('ETC/BTC') + freqtrade.handle_protections('ETC/BTC', '*') send_msg_mock = freqtrade.rpc.send_msg assert send_msg_mock.call_count == 2 assert send_msg_mock.call_args_list[0][0][0]['type'] == RPCMessageType.PROTECTION_TRIGGER From 7c79d937e0a82f41015c73666092f403d0a11eb2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Apr 2022 10:58:21 +0200 Subject: [PATCH 152/254] Properly type "side" parameter --- freqtrade/freqtradebot.py | 2 +- freqtrade/optimize/backtesting.py | 3 ++- freqtrade/persistence/models.py | 4 ++-- freqtrade/plugins/protectionmanager.py | 8 +++++--- freqtrade/plugins/protections/cooldown_period.py | 6 ++++-- freqtrade/plugins/protections/iprotection.py | 6 ++++-- freqtrade/plugins/protections/low_profit_pairs.py | 6 ++++-- freqtrade/plugins/protections/max_drawdown_protection.py | 6 ++++-- freqtrade/plugins/protections/stoploss_guard.py | 6 ++++-- 9 files changed, 30 insertions(+), 17 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d3408ada2..833c80735 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1611,7 +1611,7 @@ class FreqtradeBot(LoggingMixin): return False - def handle_protections(self, pair: str, side: str) -> None: + def handle_protections(self, pair: str, side: LongShort) -> None: prot_trig = self.protections.stop_per_pair(pair, side=side) if prot_trig: msg = {'type': RPCMessageType.PROTECTION_TRIGGER, } diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 86c52e737..3c41967e3 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -849,7 +849,8 @@ class Backtesting: return 'short' return None - def run_protections(self, enable_protections, pair: str, current_time: datetime, side: str): + def run_protections( + self, enable_protections, pair: str, current_time: datetime, side: LongShort): if enable_protections: self.protections.stop_per_pair(pair, current_time, side) self.protections.global_stop(current_time, side) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 4aa1c6a4d..98aeacee9 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -13,7 +13,7 @@ from sqlalchemy.orm import Query, declarative_base, relationship, scoped_session from sqlalchemy.pool import StaticPool from sqlalchemy.sql.schema import UniqueConstraint -from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES +from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES, LongShort from freqtrade.enums import ExitType, TradingMode from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.leverage import interest @@ -393,7 +393,7 @@ class LocalTrade(): return "sell" @property - def trade_direction(self) -> str: + def trade_direction(self) -> LongShort: if self.is_short: return "short" else: diff --git a/freqtrade/plugins/protectionmanager.py b/freqtrade/plugins/protectionmanager.py index 6a54c4369..d46826605 100644 --- a/freqtrade/plugins/protectionmanager.py +++ b/freqtrade/plugins/protectionmanager.py @@ -5,6 +5,7 @@ import logging from datetime import datetime, timezone from typing import Dict, List, Optional +from freqtrade.constants import LongShort from freqtrade.persistence import PairLocks from freqtrade.persistence.models import PairLock from freqtrade.plugins.protections import IProtection @@ -44,7 +45,8 @@ class ProtectionManager(): """ return [{p.name: p.short_desc()} for p in self._protection_handlers] - def global_stop(self, now: Optional[datetime] = None, side: str = 'long') -> Optional[PairLock]: + def global_stop(self, now: Optional[datetime] = None, + side: LongShort = 'long') -> Optional[PairLock]: if not now: now = datetime.now(timezone.utc) result = None @@ -56,8 +58,8 @@ class ProtectionManager(): result = PairLocks.lock_pair('*', lock.until, lock.reason, now=now) return result - def stop_per_pair( - self, pair, now: Optional[datetime] = None, side: str = 'long') -> Optional[PairLock]: + def stop_per_pair(self, pair, now: Optional[datetime] = None, + side: LongShort = 'long') -> Optional[PairLock]: if not now: now = datetime.now(timezone.utc) result = None diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py index a1d7d4291..426b8f1b6 100644 --- a/freqtrade/plugins/protections/cooldown_period.py +++ b/freqtrade/plugins/protections/cooldown_period.py @@ -3,6 +3,7 @@ import logging from datetime import datetime, timedelta from typing import Optional +from freqtrade.constants import LongShort from freqtrade.persistence import Trade from freqtrade.plugins.protections import IProtection, ProtectionReturn @@ -54,7 +55,7 @@ class CooldownPeriod(IProtection): return None - def global_stop(self, date_now: datetime, side: str) -> Optional[ProtectionReturn]: + def global_stop(self, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". @@ -64,7 +65,8 @@ class CooldownPeriod(IProtection): # Not implemented for cooldown period. return None - def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> Optional[ProtectionReturn]: + def stop_per_pair( + self, pair: str, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for this pair This must evaluate to true for the whole period of the "cooldown period". diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index 0eff796b3..5ec1c0779 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -5,6 +5,7 @@ from dataclasses import dataclass from datetime import datetime, timedelta, timezone from typing import Any, Dict, List, Optional +from freqtrade.constants import LongShort from freqtrade.exchange import timeframe_to_minutes from freqtrade.misc import plural from freqtrade.mixins import LoggingMixin @@ -87,14 +88,15 @@ class IProtection(LoggingMixin, ABC): """ @abstractmethod - def global_stop(self, date_now: datetime, side: str) -> Optional[ProtectionReturn]: + def global_stop(self, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". """ @abstractmethod - def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> Optional[ProtectionReturn]: + def stop_per_pair( + self, pair: str, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for this pair This must evaluate to true for the whole period of the "cooldown period". diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py index a4b09bb66..7d5d6054d 100644 --- a/freqtrade/plugins/protections/low_profit_pairs.py +++ b/freqtrade/plugins/protections/low_profit_pairs.py @@ -3,6 +3,7 @@ import logging from datetime import datetime, timedelta from typing import Any, Dict, Optional +from freqtrade.constants import LongShort from freqtrade.persistence import Trade from freqtrade.plugins.protections import IProtection, ProtectionReturn @@ -68,7 +69,7 @@ class LowProfitPairs(IProtection): return None - def global_stop(self, date_now: datetime, side: str) -> Optional[ProtectionReturn]: + def global_stop(self, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". @@ -77,7 +78,8 @@ class LowProfitPairs(IProtection): """ return None - def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> Optional[ProtectionReturn]: + def stop_per_pair( + self, pair: str, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for this pair This must evaluate to true for the whole period of the "cooldown period". diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index f489522cf..d759a23dd 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -5,6 +5,7 @@ from typing import Any, Dict, Optional import pandas as pd +from freqtrade.constants import LongShort from freqtrade.data.btanalysis import calculate_max_drawdown from freqtrade.persistence import Trade from freqtrade.plugins.protections import IProtection, ProtectionReturn @@ -75,7 +76,7 @@ class MaxDrawdown(IProtection): return None - def global_stop(self, date_now: datetime, side: str) -> Optional[ProtectionReturn]: + def global_stop(self, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". @@ -84,7 +85,8 @@ class MaxDrawdown(IProtection): """ return self._max_drawdown(date_now) - def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> Optional[ProtectionReturn]: + def stop_per_pair( + self, pair: str, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for this pair This must evaluate to true for the whole period of the "cooldown period". diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index bb442575e..d0ac2783d 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -3,6 +3,7 @@ import logging from datetime import datetime, timedelta from typing import Any, Dict, Optional +from freqtrade.constants import LongShort from freqtrade.enums import ExitType from freqtrade.persistence import Trade from freqtrade.plugins.protections import IProtection, ProtectionReturn @@ -67,7 +68,7 @@ class StoplossGuard(IProtection): lock_side=(side if self._only_per_side else None) ) - def global_stop(self, date_now: datetime, side: str) -> Optional[ProtectionReturn]: + def global_stop(self, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for all pairs This must evaluate to true for the whole period of the "cooldown period". @@ -78,7 +79,8 @@ class StoplossGuard(IProtection): return None return self._stoploss_guard(date_now, None, side) - def stop_per_pair(self, pair: str, date_now: datetime, side: str) -> Optional[ProtectionReturn]: + def stop_per_pair( + self, pair: str, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]: """ Stops trading (position entering) for this pair This must evaluate to true for the whole period of the "cooldown period". From 420836b1b20a24ec07b345909871a9743c7bfa36 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Apr 2022 11:23:04 +0200 Subject: [PATCH 153/254] Update test naming --- tests/plugins/test_protections.py | 64 ++++++++++++++++--------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index c8a3b7a82..8ad712e34 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -11,9 +11,10 @@ from tests.conftest import get_patched_freqtradebot, log_has_re def generate_mock_trade(pair: str, fee: float, is_open: bool, - sell_reason: str = ExitType.EXIT_SIGNAL, + exit_reason: str = ExitType.EXIT_SIGNAL, min_ago_open: int = None, min_ago_close: int = None, - profit_rate: float = 0.9 + profit_rate: float = 0.9, + is_short: bool = False, ): open_rate = random.random() @@ -28,11 +29,12 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool, is_open=is_open, amount=0.01 / open_rate, exchange='binance', + is_short=is_short, ) trade.recalc_open_trade_value() if not is_open: - trade.close(open_rate * profit_rate) - trade.exit_reason = sell_reason + trade.close(open_rate * (2 - profit_rate if is_short else profit_rate)) + trade.exit_reason = exit_reason return trade @@ -76,8 +78,10 @@ def test_protections_init(mocker, default_conf, timeframe, expected, protconf): assert man._protection_handlers[0]._stop_duration == expected[1] +@pytest.mark.parametrize('is_short', [False, True]) @pytest.mark.usefixtures("init_persistence") -def test_stoploss_guard(mocker, default_conf, fee, caplog): +def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short): + # Active for both sides (long and short) default_conf['protections'] = [{ "method": "StoplossGuard", "lookback_period": 60, @@ -91,8 +95,8 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog): caplog.clear() Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, - min_ago_open=200, min_ago_close=30, + 'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, + min_ago_open=200, min_ago_close=30, is_short=is_short, )) assert not freqtrade.protections.global_stop() @@ -100,13 +104,13 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog): caplog.clear() # This trade does not count, as it's closed too long ago Trade.query.session.add(generate_mock_trade( - 'BCH/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, - min_ago_open=250, min_ago_close=100, + 'BCH/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, + min_ago_open=250, min_ago_close=100, is_short=is_short, )) Trade.query.session.add(generate_mock_trade( - 'ETH/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, - min_ago_open=240, min_ago_close=30, + 'ETH/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, + min_ago_open=240, min_ago_close=30, is_short=is_short, )) # 3 Trades closed - but the 2nd has been closed too long ago. assert not freqtrade.protections.global_stop() @@ -114,8 +118,8 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog): caplog.clear() Trade.query.session.add(generate_mock_trade( - 'LTC/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, - min_ago_open=180, min_ago_close=30, + 'LTC/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, + min_ago_open=180, min_ago_close=30, is_short=is_short, )) assert freqtrade.protections.global_stop() @@ -148,7 +152,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair caplog.clear() Trade.query.session.add(generate_mock_trade( - pair, fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, + pair, fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, min_ago_open=200, min_ago_close=30, profit_rate=0.9, )) @@ -158,12 +162,12 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair caplog.clear() # This trade does not count, as it's closed too long ago Trade.query.session.add(generate_mock_trade( - pair, fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, + pair, fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, min_ago_open=250, min_ago_close=100, profit_rate=0.9, )) # Trade does not count for per pair stop as it's the wrong pair. Trade.query.session.add(generate_mock_trade( - 'ETH/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, + 'ETH/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, min_ago_open=240, min_ago_close=30, profit_rate=0.9, )) # 3 Trades closed - but the 2nd has been closed too long ago. @@ -178,7 +182,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair # 2nd Trade that counts with correct pair Trade.query.session.add(generate_mock_trade( - pair, fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, + pair, fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, min_ago_open=180, min_ago_close=30, profit_rate=0.9, )) @@ -203,7 +207,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog): caplog.clear() Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, + 'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, min_ago_open=200, min_ago_close=30, )) @@ -213,7 +217,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog): assert not PairLocks.is_global_lock() Trade.query.session.add(generate_mock_trade( - 'ETH/BTC', fee.return_value, False, sell_reason=ExitType.ROI.value, + 'ETH/BTC', fee.return_value, False, exit_reason=ExitType.ROI.value, min_ago_open=205, min_ago_close=35, )) @@ -242,7 +246,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog): caplog.clear() Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, + 'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, min_ago_open=800, min_ago_close=450, profit_rate=0.9, )) @@ -253,7 +257,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog): assert not PairLocks.is_global_lock() Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, + 'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, min_ago_open=200, min_ago_close=120, profit_rate=0.9, )) @@ -265,14 +269,14 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog): # Add positive trade Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.ROI.value, + 'XRP/BTC', fee.return_value, False, exit_reason=ExitType.ROI.value, min_ago_open=20, min_ago_close=10, profit_rate=1.15, )) assert not freqtrade.protections.stop_per_pair('XRP/BTC') assert not PairLocks.is_pair_locked('XRP/BTC') Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, + 'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, min_ago_open=110, min_ago_close=20, profit_rate=0.8, )) @@ -300,15 +304,15 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): caplog.clear() Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, + 'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, min_ago_open=1000, min_ago_close=900, profit_rate=1.1, )) Trade.query.session.add(generate_mock_trade( - 'ETH/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, + 'ETH/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, min_ago_open=1000, min_ago_close=900, profit_rate=1.1, )) Trade.query.session.add(generate_mock_trade( - 'NEO/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, + 'NEO/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, min_ago_open=1000, min_ago_close=900, profit_rate=1.1, )) # No losing trade yet ... so max_drawdown will raise exception @@ -316,7 +320,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): assert not freqtrade.protections.stop_per_pair('XRP/BTC') Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, + 'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, min_ago_open=500, min_ago_close=400, profit_rate=0.9, )) # Not locked with one trade @@ -326,7 +330,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): assert not PairLocks.is_global_lock() Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.STOP_LOSS.value, + 'XRP/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, min_ago_open=1200, min_ago_close=1100, profit_rate=0.5, )) @@ -339,7 +343,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): # Winning trade ... (should not lock, does not change drawdown!) Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.ROI.value, + 'XRP/BTC', fee.return_value, False, exit_reason=ExitType.ROI.value, min_ago_open=320, min_ago_close=410, profit_rate=1.5, )) assert not freqtrade.protections.global_stop() @@ -349,7 +353,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog): # Add additional negative trade, causing a loss of > 15% Trade.query.session.add(generate_mock_trade( - 'XRP/BTC', fee.return_value, False, sell_reason=ExitType.ROI.value, + 'XRP/BTC', fee.return_value, False, exit_reason=ExitType.ROI.value, min_ago_open=20, min_ago_close=10, profit_rate=0.8, )) assert not freqtrade.protections.stop_per_pair('XRP/BTC') From fc201bb4ffbda7e74b67f2674e07160f7aa5c14a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Apr 2022 11:23:26 +0200 Subject: [PATCH 154/254] implement pairlock side further --- freqtrade/persistence/models.py | 5 ++++- freqtrade/persistence/pairlock_middleware.py | 21 ++++++++++++------- freqtrade/plugins/protectionmanager.py | 10 +++++---- freqtrade/plugins/protections/iprotection.py | 2 +- .../plugins/protections/stoploss_guard.py | 2 +- freqtrade/strategy/interface.py | 4 ++-- 6 files changed, 28 insertions(+), 16 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 98aeacee9..611b084a9 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -1445,7 +1445,7 @@ class PairLock(_DECL_BASE): f'lock_end_time={lock_end_time}, reason={self.reason}, active={self.active})') @staticmethod - def query_pair_locks(pair: Optional[str], now: datetime) -> Query: + def query_pair_locks(pair: Optional[str], now: datetime, side: str = '*') -> Query: """ Get all currently active locks for this pair :param pair: Pair to check for. Returns all current locks if pair is empty @@ -1456,6 +1456,9 @@ class PairLock(_DECL_BASE): PairLock.active.is_(True), ] if pair: filters.append(PairLock.pair == pair) + if side != '*': + filters.append(PairLock.direction == side) + return PairLock.query.filter( *filters ) diff --git a/freqtrade/persistence/pairlock_middleware.py b/freqtrade/persistence/pairlock_middleware.py index afbd9781b..b8a092365 100644 --- a/freqtrade/persistence/pairlock_middleware.py +++ b/freqtrade/persistence/pairlock_middleware.py @@ -31,7 +31,7 @@ class PairLocks(): @staticmethod def lock_pair(pair: str, until: datetime, reason: str = None, *, - now: datetime = None) -> PairLock: + now: datetime = None, side: str) -> PairLock: """ Create PairLock from now to "until". Uses database by default, unless PairLocks.use_db is set to False, @@ -40,12 +40,14 @@ class PairLocks(): :param until: End time of the lock. Will be rounded up to the next candle. :param reason: Reason string that will be shown as reason for the lock :param now: Current timestamp. Used to determine lock start time. + :param side: Side to lock pair, can be 'long', 'short' or '*' """ lock = PairLock( pair=pair, lock_time=now or datetime.now(timezone.utc), lock_end_time=timeframe_to_next_date(PairLocks.timeframe, until), reason=reason, + direction=side, active=True ) if PairLocks.use_db: @@ -56,7 +58,8 @@ class PairLocks(): return lock @staticmethod - def get_pair_locks(pair: Optional[str], now: Optional[datetime] = None) -> List[PairLock]: + def get_pair_locks( + pair: Optional[str], now: Optional[datetime] = None, side: str = '*') -> List[PairLock]: """ Get all currently active locks for this pair :param pair: Pair to check for. Returns all current locks if pair is empty @@ -67,12 +70,13 @@ class PairLocks(): now = datetime.now(timezone.utc) if PairLocks.use_db: - return PairLock.query_pair_locks(pair, now).all() + return PairLock.query_pair_locks(pair, now, side).all() else: locks = [lock for lock in PairLocks.locks if ( lock.lock_end_time >= now and lock.active is True and (pair is None or lock.pair == pair) + and (side == '*' or lock.direction == side) )] return locks @@ -134,7 +138,7 @@ class PairLocks(): lock.active = False @staticmethod - def is_global_lock(now: Optional[datetime] = None) -> bool: + def is_global_lock(now: Optional[datetime] = None, side: str = '*') -> bool: """ :param now: Datetime object (generated via datetime.now(timezone.utc)). defaults to datetime.now(timezone.utc) @@ -142,10 +146,10 @@ class PairLocks(): if not now: now = datetime.now(timezone.utc) - return len(PairLocks.get_pair_locks('*', now)) > 0 + return len(PairLocks.get_pair_locks('*', now, side)) > 0 @staticmethod - def is_pair_locked(pair: str, now: Optional[datetime] = None) -> bool: + def is_pair_locked(pair: str, now: Optional[datetime] = None, side: str = '*') -> bool: """ :param pair: Pair to check for :param now: Datetime object (generated via datetime.now(timezone.utc)). @@ -154,7 +158,10 @@ class PairLocks(): if not now: now = datetime.now(timezone.utc) - return len(PairLocks.get_pair_locks(pair, now)) > 0 or PairLocks.is_global_lock(now) + return ( + len(PairLocks.get_pair_locks(pair, now, side)) > 0 + or PairLocks.is_global_lock(now, side) + ) @staticmethod def get_all_locks() -> List[PairLock]: diff --git a/freqtrade/plugins/protectionmanager.py b/freqtrade/plugins/protectionmanager.py index d46826605..4868f2c33 100644 --- a/freqtrade/plugins/protectionmanager.py +++ b/freqtrade/plugins/protectionmanager.py @@ -54,8 +54,9 @@ class ProtectionManager(): if protection_handler.has_global_stop: lock = protection_handler.global_stop(date_now=now, side=side) if lock and lock.until: - if not PairLocks.is_global_lock(lock.until): - result = PairLocks.lock_pair('*', lock.until, lock.reason, now=now) + if not PairLocks.is_global_lock(lock.until, lock.lock_side): + result = PairLocks.lock_pair( + '*', lock.until, lock.reason, now=now, side=lock.lock_side) return result def stop_per_pair(self, pair, now: Optional[datetime] = None, @@ -68,6 +69,7 @@ class ProtectionManager(): lock = protection_handler.stop_per_pair( pair=pair, date_now=now, side=side) if lock and lock.until: - if not PairLocks.is_pair_locked(pair, lock.until): - result = PairLocks.lock_pair(pair, lock.until, lock.reason, now=now) + if not PairLocks.is_pair_locked(pair, lock.until, lock.lock_side): + result = PairLocks.lock_pair( + pair, lock.until, lock.reason, now=now, side=lock.lock_side) return result diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py index 5ec1c0779..890988226 100644 --- a/freqtrade/plugins/protections/iprotection.py +++ b/freqtrade/plugins/protections/iprotection.py @@ -20,7 +20,7 @@ class ProtectionReturn: lock: bool until: datetime reason: Optional[str] - lock_side: Optional[str] = None + lock_side: str = '*' class IProtection(LoggingMixin, ABC): diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index d0ac2783d..1943513ca 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -65,7 +65,7 @@ class StoplossGuard(IProtection): lock=True, until=until, reason=self._reason(), - lock_side=(side if self._only_per_side else None) + lock_side=(side if self._only_per_side else '*') ) def global_stop(self, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index afcc1aa99..0a20de08b 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -541,7 +541,7 @@ class IStrategy(ABC, HyperStrategyMixin): """ return self.__class__.__name__ - def lock_pair(self, pair: str, until: datetime, reason: str = None) -> None: + def lock_pair(self, pair: str, until: datetime, reason: str = None, side: str = '*') -> None: """ Locks pair until a given timestamp happens. Locked pairs are not analyzed, and are prevented from opening new trades. @@ -552,7 +552,7 @@ class IStrategy(ABC, HyperStrategyMixin): Needs to be timezone aware `datetime.now(timezone.utc)` :param reason: Optional string explaining why the pair was locked. """ - PairLocks.lock_pair(pair, until, reason) + PairLocks.lock_pair(pair, until, reason, side=side) def unlock_pair(self, pair: str) -> None: """ From 845f960a4e10ee46b188aab47cabcc931279047b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Apr 2022 11:24:15 +0200 Subject: [PATCH 155/254] realign pairlock naming to side --- freqtrade/persistence/migrations.py | 6 +++--- freqtrade/persistence/models.py | 4 ++-- freqtrade/persistence/pairlock_middleware.py | 6 +++--- tests/test_freqtradebot.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index eff2d69f3..93c70b70d 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -219,16 +219,16 @@ def migrate_pairlocks_table( drop_index_on_table(engine, inspector, pairlock_back_name) - direction = get_column_def(cols, 'direction', "'*'") + side = get_column_def(cols, 'side', "'*'") # let SQLAlchemy create the schema as required decl_base.metadata.create_all(engine) # Copy data back - following the correct schema with engine.begin() as connection: connection.execute(text(f"""insert into pairlocks - (id, pair, direction, reason, lock_time, + (id, pair, side, reason, lock_time, lock_end_time, active) - select id, pair, {direction} direction, reason, lock_time, + select id, pair, {side} side, reason, lock_time, lock_end_time, active from {pairlock_back_name} """)) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 611b084a9..1c219610d 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -1429,7 +1429,7 @@ class PairLock(_DECL_BASE): pair = Column(String(25), nullable=False, index=True) # lock direction - long, short or * (for both) - direction = Column(String(25), nullable=False, default="*") + side = Column(String(25), nullable=False, default="*") reason = Column(String(255), nullable=True) # Time the pair was locked (start time) lock_time = Column(DateTime, nullable=False) @@ -1457,7 +1457,7 @@ class PairLock(_DECL_BASE): if pair: filters.append(PairLock.pair == pair) if side != '*': - filters.append(PairLock.direction == side) + filters.append(PairLock.side == side) return PairLock.query.filter( *filters diff --git a/freqtrade/persistence/pairlock_middleware.py b/freqtrade/persistence/pairlock_middleware.py index b8a092365..ade92355c 100644 --- a/freqtrade/persistence/pairlock_middleware.py +++ b/freqtrade/persistence/pairlock_middleware.py @@ -31,7 +31,7 @@ class PairLocks(): @staticmethod def lock_pair(pair: str, until: datetime, reason: str = None, *, - now: datetime = None, side: str) -> PairLock: + now: datetime = None, side: str = '*') -> PairLock: """ Create PairLock from now to "until". Uses database by default, unless PairLocks.use_db is set to False, @@ -47,7 +47,7 @@ class PairLocks(): lock_time=now or datetime.now(timezone.utc), lock_end_time=timeframe_to_next_date(PairLocks.timeframe, until), reason=reason, - direction=side, + side=side, active=True ) if PairLocks.use_db: @@ -76,7 +76,7 @@ class PairLocks(): lock.lock_end_time >= now and lock.active is True and (pair is None or lock.pair == pair) - and (side == '*' or lock.direction == side) + and (side == '*' or lock.side == side) )] return locks diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 0ae36f0fd..7bb728c66 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -421,7 +421,7 @@ def test_enter_positions_global_pairlock(default_conf_usdt, ticker_usdt, limit_b assert not log_has_re(message, caplog) caplog.clear() - PairLocks.lock_pair('*', arrow.utcnow().shift(minutes=20).datetime, 'Just because') + PairLocks.lock_pair('*', arrow.utcnow().shift(minutes=20).datetime, 'Just because', side='*') n = freqtrade.enter_positions() assert n == 0 assert log_has_re(message, caplog) From 4942d73693e18926029acc0769890b315033d1ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Apr 2022 11:47:28 +0200 Subject: [PATCH 156/254] update pairlock tests --- freqtrade/persistence/models.py | 11 +++++++---- freqtrade/persistence/pairlock_middleware.py | 2 +- tests/plugins/test_pairlocks.py | 16 +++++++++++++++- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 1c219610d..1ff38e001 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -7,7 +7,7 @@ from decimal import Decimal from typing import Any, Dict, List, Optional from sqlalchemy import (Boolean, Column, DateTime, Enum, Float, ForeignKey, Integer, String, - create_engine, desc, func, inspect) + create_engine, desc, func, inspect, or_) from sqlalchemy.exc import NoSuchModuleError from sqlalchemy.orm import Query, declarative_base, relationship, scoped_session, sessionmaker from sqlalchemy.pool import StaticPool @@ -1441,8 +1441,9 @@ class PairLock(_DECL_BASE): def __repr__(self): lock_time = self.lock_time.strftime(DATETIME_PRINT_FORMAT) lock_end_time = self.lock_end_time.strftime(DATETIME_PRINT_FORMAT) - return (f'PairLock(id={self.id}, pair={self.pair}, lock_time={lock_time}, ' - f'lock_end_time={lock_end_time}, reason={self.reason}, active={self.active})') + return ( + f'PairLock(id={self.id}, pair={self.pair}, side={self.side}, lock_time={lock_time}, ' + f'lock_end_time={lock_end_time}, reason={self.reason}, active={self.active})') @staticmethod def query_pair_locks(pair: Optional[str], now: datetime, side: str = '*') -> Query: @@ -1457,7 +1458,9 @@ class PairLock(_DECL_BASE): if pair: filters.append(PairLock.pair == pair) if side != '*': - filters.append(PairLock.side == side) + filters.append(or_(PairLock.side == side, PairLock.side == '*')) + else: + filters.append(PairLock.side == '*') return PairLock.query.filter( *filters diff --git a/freqtrade/persistence/pairlock_middleware.py b/freqtrade/persistence/pairlock_middleware.py index ade92355c..fc727acf5 100644 --- a/freqtrade/persistence/pairlock_middleware.py +++ b/freqtrade/persistence/pairlock_middleware.py @@ -76,7 +76,7 @@ class PairLocks(): lock.lock_end_time >= now and lock.active is True and (pair is None or lock.pair == pair) - and (side == '*' or lock.side == side) + and (lock.side == '*' or lock.side == side) )] return locks diff --git a/tests/plugins/test_pairlocks.py b/tests/plugins/test_pairlocks.py index f9e5583ed..0ba9bb746 100644 --- a/tests/plugins/test_pairlocks.py +++ b/tests/plugins/test_pairlocks.py @@ -21,8 +21,22 @@ def test_PairLocks(use_db): pair = 'ETH/BTC' assert not PairLocks.is_pair_locked(pair) PairLocks.lock_pair(pair, arrow.utcnow().shift(minutes=4).datetime) - # ETH/BTC locked for 4 minutes + # ETH/BTC locked for 4 minutes (on both sides) assert PairLocks.is_pair_locked(pair) + assert PairLocks.is_pair_locked(pair, side='long') + assert PairLocks.is_pair_locked(pair, side='short') + + pair = 'BNB/BTC' + PairLocks.lock_pair(pair, arrow.utcnow().shift(minutes=4).datetime, side='long') + assert not PairLocks.is_pair_locked(pair) + assert PairLocks.is_pair_locked(pair, side='long') + assert not PairLocks.is_pair_locked(pair, side='short') + + pair = 'BNB/USDT' + PairLocks.lock_pair(pair, arrow.utcnow().shift(minutes=4).datetime, side='short') + assert not PairLocks.is_pair_locked(pair) + assert not PairLocks.is_pair_locked(pair, side='long') + assert PairLocks.is_pair_locked(pair, side='short') # XRP/BTC should not be locked now pair = 'XRP/BTC' From b0a8bf3025c63829bdf6487e04e2cbbaa34f4779 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Apr 2022 11:51:33 +0200 Subject: [PATCH 157/254] Show lock side --- freqtrade/persistence/models.py | 1 + freqtrade/rpc/api_server/api_schemas.py | 1 + 2 files changed, 2 insertions(+) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 1ff38e001..843db4691 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -1476,5 +1476,6 @@ class PairLock(_DECL_BASE): 'lock_end_timestamp': int(self.lock_end_time.replace(tzinfo=timezone.utc ).timestamp() * 1000), 'reason': self.reason, + 'side': self.side, 'active': self.active, } diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index a9135cce2..d78ea8b78 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -291,6 +291,7 @@ class LockModel(BaseModel): lock_time: str lock_timestamp: int pair: str + side: str reason: str From 144e4da96e947a9c41cf7de54b9d1aff4e12c353 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Apr 2022 12:07:19 +0200 Subject: [PATCH 158/254] Update stoploss guard tests --- tests/plugins/test_protections.py | 40 ++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 8ad712e34..b2dc99610 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -70,7 +70,7 @@ def test_protectionmanager(mocker, default_conf): ('1h', [60, 540], [{"method": "StoplossGuard", "lookback_period_candles": 1, "stop_duration_candles": 9}]), ]) -def test_protections_init(mocker, default_conf, timeframe, expected, protconf): +def test_protections_init(default_conf, timeframe, expected, protconf): default_conf['timeframe'] = timeframe man = ProtectionManager(default_conf, protconf) assert len(man._protection_handlers) == len(protconf) @@ -134,15 +134,19 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog, is_short): @pytest.mark.parametrize('only_per_pair', [False, True]) +@pytest.mark.parametrize('only_per_side', [False, True]) @pytest.mark.usefixtures("init_persistence") -def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair): +def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair, only_per_side): default_conf['protections'] = [{ "method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2, "stop_duration": 60, - "only_per_pair": only_per_pair + "only_per_pair": only_per_pair, + "only_per_side": only_per_side, }] + check_side = 'long' if only_per_side else '*' + is_short = False freqtrade = get_patched_freqtradebot(mocker, default_conf) message = r"Trading stopped due to .*" pair = 'XRP/BTC' @@ -153,7 +157,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair Trade.query.session.add(generate_mock_trade( pair, fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, - min_ago_open=200, min_ago_close=30, profit_rate=0.9, + min_ago_open=200, min_ago_close=30, profit_rate=0.9, is_short=is_short )) assert not freqtrade.protections.stop_per_pair(pair) @@ -163,12 +167,12 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair # This trade does not count, as it's closed too long ago Trade.query.session.add(generate_mock_trade( pair, fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, - min_ago_open=250, min_ago_close=100, profit_rate=0.9, + min_ago_open=250, min_ago_close=100, profit_rate=0.9, is_short=is_short )) # Trade does not count for per pair stop as it's the wrong pair. Trade.query.session.add(generate_mock_trade( 'ETH/BTC', fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, - min_ago_open=240, min_ago_close=30, profit_rate=0.9, + min_ago_open=240, min_ago_close=30, profit_rate=0.9, is_short=is_short )) # 3 Trades closed - but the 2nd has been closed too long ago. assert not freqtrade.protections.stop_per_pair(pair) @@ -180,16 +184,34 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair caplog.clear() + # Trade does not count potentially, as it's in the wrong direction + Trade.query.session.add(generate_mock_trade( + pair, fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, + min_ago_open=150, min_ago_close=25, profit_rate=0.9, is_short=not is_short + )) + freqtrade.protections.stop_per_pair(pair) + assert freqtrade.protections.global_stop() != only_per_pair + assert PairLocks.is_pair_locked(pair, side=check_side) != (only_per_side and only_per_pair) + assert PairLocks.is_global_lock(side=check_side) != only_per_pair + if only_per_side: + assert not PairLocks.is_pair_locked(pair, side='*') + assert not PairLocks.is_global_lock(side='*') + + caplog.clear() + # 2nd Trade that counts with correct pair Trade.query.session.add(generate_mock_trade( pair, fee.return_value, False, exit_reason=ExitType.STOP_LOSS.value, - min_ago_open=180, min_ago_close=30, profit_rate=0.9, + min_ago_open=180, min_ago_close=30, profit_rate=0.9, is_short=is_short )) freqtrade.protections.stop_per_pair(pair) assert freqtrade.protections.global_stop() != only_per_pair - assert PairLocks.is_pair_locked(pair) - assert PairLocks.is_global_lock() != only_per_pair + assert PairLocks.is_pair_locked(pair, side=check_side) + assert PairLocks.is_global_lock(side=check_side) != only_per_pair + if only_per_side: + assert not PairLocks.is_pair_locked(pair, side='*') + assert not PairLocks.is_global_lock(side='*') @pytest.mark.usefixtures("init_persistence") From 737bdfe844e575bdbbc9cd9d2a84291fe2e58300 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Apr 2022 14:10:25 +0200 Subject: [PATCH 159/254] Use "side" parameter when calling Pairlocks --- freqtrade/freqtradebot.py | 25 +++++++++++--------- freqtrade/optimize/backtesting.py | 2 +- freqtrade/persistence/migrations.py | 2 +- freqtrade/persistence/pairlock_middleware.py | 9 +++---- freqtrade/plugins/protectionmanager.py | 2 +- freqtrade/strategy/interface.py | 7 +++--- tests/strategy/test_interface.py | 14 +++++------ tests/test_freqtradebot.py | 7 ++++-- tests/test_persistence.py | 5 +++- 9 files changed, 42 insertions(+), 31 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 833c80735..dadfaa5b9 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -399,7 +399,10 @@ class FreqtradeBot(LoggingMixin): logger.info("No currency pair in active pair whitelist, " "but checking to exit open trades.") return trades_created - if PairLocks.is_global_lock(): + if PairLocks.is_global_lock(side='*'): + # This only checks for total locks (both sides). + # per-side locks will be evaluated by `is_pair_locked` within create_trade, + # once the direction for the trade is clear. lock = PairLocks.get_pair_longest_lock('*') if lock: self.log_once(f"Global pairlock active until " @@ -433,16 +436,6 @@ class FreqtradeBot(LoggingMixin): analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(pair, self.strategy.timeframe) nowtime = analyzed_df.iloc[-1]['date'] if len(analyzed_df) > 0 else None - if self.strategy.is_pair_locked(pair, nowtime): - lock = PairLocks.get_pair_longest_lock(pair, nowtime) - if lock: - self.log_once(f"Pair {pair} is still locked until " - f"{lock.lock_end_time.strftime(constants.DATETIME_PRINT_FORMAT)} " - f"due to {lock.reason}.", - logger.info) - else: - self.log_once(f"Pair {pair} is still locked.", logger.info) - return False # get_free_open_trades is checked before create_trade is called # but it is still used here to prevent opening too many trades within one iteration @@ -458,6 +451,16 @@ class FreqtradeBot(LoggingMixin): ) if signal: + if self.strategy.is_pair_locked(pair, candle_date=nowtime, side=signal): + lock = PairLocks.get_pair_longest_lock(pair, nowtime, signal) + if lock: + self.log_once(f"Pair {pair} {lock.side} is locked until " + f"{lock.lock_end_time.strftime(constants.DATETIME_PRINT_FORMAT)} " + f"due to {lock.reason}.", + logger.info) + else: + self.log_once(f"Pair {pair} is currently locked.", logger.info) + return False stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge) bid_check_dom = self.config.get('entry_pricing', {}).get('check_depth_of_market', {}) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 3c41967e3..260f8e84f 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -965,7 +965,7 @@ class Backtesting: and self.trade_slot_available(max_open_trades, open_trade_count_start) and current_time != end_date and trade_dir is not None - and not PairLocks.is_pair_locked(pair, row[DATE_IDX]) + and not PairLocks.is_pair_locked(pair, row[DATE_IDX], trade_dir) ): trade = self._enter_trade(pair, row, trade_dir) if trade: diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 93c70b70d..03f3c3fb9 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -268,7 +268,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None: decl_base, inspector, engine, table_back_name, cols_trades, order_table_bak_name, cols_orders) - if not has_column(cols_pairlocks, 'direction'): + if not has_column(cols_pairlocks, 'side'): logger.info(f"Running database migration for pairlocks - " f"backup: {pairlock_table_bak_name}") diff --git a/freqtrade/persistence/pairlock_middleware.py b/freqtrade/persistence/pairlock_middleware.py index fc727acf5..ec57e91fc 100644 --- a/freqtrade/persistence/pairlock_middleware.py +++ b/freqtrade/persistence/pairlock_middleware.py @@ -81,16 +81,17 @@ class PairLocks(): return locks @staticmethod - def get_pair_longest_lock(pair: str, now: Optional[datetime] = None) -> Optional[PairLock]: + def get_pair_longest_lock( + pair: str, now: Optional[datetime] = None, side: str = '*') -> Optional[PairLock]: """ Get the lock that expires the latest for the pair given. """ - locks = PairLocks.get_pair_locks(pair, now) + locks = PairLocks.get_pair_locks(pair, now, side=side) locks = sorted(locks, key=lambda l: l.lock_end_time, reverse=True) return locks[0] if locks else None @staticmethod - def unlock_pair(pair: str, now: Optional[datetime] = None) -> None: + def unlock_pair(pair: str, now: Optional[datetime] = None, side: str = '*') -> None: """ Release all locks for this pair. :param pair: Pair to unlock @@ -101,7 +102,7 @@ class PairLocks(): now = datetime.now(timezone.utc) logger.info(f"Releasing all locks for {pair}.") - locks = PairLocks.get_pair_locks(pair, now) + locks = PairLocks.get_pair_locks(pair, now, side=side) for lock in locks: lock.active = False if PairLocks.use_db: diff --git a/freqtrade/plugins/protectionmanager.py b/freqtrade/plugins/protectionmanager.py index 4868f2c33..d33294fa7 100644 --- a/freqtrade/plugins/protectionmanager.py +++ b/freqtrade/plugins/protectionmanager.py @@ -54,7 +54,7 @@ class ProtectionManager(): if protection_handler.has_global_stop: lock = protection_handler.global_stop(date_now=now, side=side) if lock and lock.until: - if not PairLocks.is_global_lock(lock.until, lock.lock_side): + if not PairLocks.is_global_lock(lock.until, side=lock.lock_side): result = PairLocks.lock_pair( '*', lock.until, lock.reason, now=now, side=lock.lock_side) return result diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 0a20de08b..7d16fc813 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -572,7 +572,7 @@ class IStrategy(ABC, HyperStrategyMixin): """ PairLocks.unlock_reason(reason, datetime.now(timezone.utc)) - def is_pair_locked(self, pair: str, candle_date: datetime = None) -> bool: + def is_pair_locked(self, pair: str, *, candle_date: datetime = None, side: str = '*') -> bool: """ Checks if a pair is currently locked The 2nd, optional parameter ensures that locks are applied until the new candle arrives, @@ -580,15 +580,16 @@ class IStrategy(ABC, HyperStrategyMixin): of 2 seconds for an entry order to happen on an old signal. :param pair: "Pair to check" :param candle_date: Date of the last candle. Optional, defaults to current date + :param side: Side to check, can be long, short or '*' :returns: locking state of the pair in question. """ if not candle_date: # Simple call ... - return PairLocks.is_pair_locked(pair) + return PairLocks.is_pair_locked(pair, side=side) else: lock_time = timeframe_to_next_date(self.timeframe, candle_date) - return PairLocks.is_pair_locked(pair, lock_time) + return PairLocks.is_pair_locked(pair, lock_time, side=side) def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index a86d69135..4dc63755f 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -666,23 +666,23 @@ def test_is_pair_locked(default_conf): assert not strategy.is_pair_locked(pair) # latest candle is from 14:20, lock goes to 14:30 - assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-10)) - assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-50)) + assert strategy.is_pair_locked(pair, candle_date=lock_time + timedelta(minutes=-10)) + assert strategy.is_pair_locked(pair, candle_date=lock_time + timedelta(minutes=-50)) # latest candle is from 14:25 (lock should be lifted) # Since this is the "new candle" available at 14:30 - assert not strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-4)) + assert not strategy.is_pair_locked(pair, candle_date=lock_time + timedelta(minutes=-4)) # Should not be locked after time expired - assert not strategy.is_pair_locked(pair, lock_time + timedelta(minutes=10)) + assert not strategy.is_pair_locked(pair, candle_date=lock_time + timedelta(minutes=10)) # Change timeframe to 15m strategy.timeframe = '15m' # Candle from 14:14 - lock goes until 14:30 - assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-16)) - assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-15, seconds=-2)) + assert strategy.is_pair_locked(pair, candle_date=lock_time + timedelta(minutes=-16)) + assert strategy.is_pair_locked(pair, candle_date=lock_time + timedelta(minutes=-15, seconds=-2)) # Candle from 14:15 - lock goes until 14:30 - assert not strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-15)) + assert not strategy.is_pair_locked(pair, candle_date=lock_time + timedelta(minutes=-15)) def test_is_informative_pairs_callback(default_conf): diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 7bb728c66..111638a81 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3796,13 +3796,16 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee, exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS) ) trade.close(ticker_usdt_sell_down()['bid']) - assert freqtrade.strategy.is_pair_locked(trade.pair) + assert freqtrade.strategy.is_pair_locked(trade.pair, side='*') + # Boths sides are locked + assert freqtrade.strategy.is_pair_locked(trade.pair, side='long') + assert freqtrade.strategy.is_pair_locked(trade.pair, side='short') # reinit - should buy other pair. caplog.clear() freqtrade.enter_positions() - assert log_has_re(f"Pair {trade.pair} is still locked.*", caplog) + assert log_has_re(fr"Pair {trade.pair} \* is locked.*", caplog) @pytest.mark.parametrize("is_short", [False, True]) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 58d3a4de4..b66c12086 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -1471,7 +1471,10 @@ def test_migrate_pairlocks(mocker, default_conf, fee, caplog): assert len(PairLock.query.all()) == 2 assert len(PairLock.query.filter(PairLock.pair == '*').all()) == 1 - assert len(PairLock.query.filter(PairLock.pair == 'ETH/BTC').all()) == 1 + pairlocks = PairLock.query.filter(PairLock.pair == 'ETH/BTC').all() + assert len(pairlocks) == 1 + pairlocks[0].pair == 'ETH/BTC' + pairlocks[0].side == '*' def test_adjust_stop_loss(fee): From 6623192108ff63f52ae6b4a0e8df67c0bc444f9f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Apr 2022 14:38:23 +0200 Subject: [PATCH 160/254] improve doc wording --- docs/includes/protections.md | 4 ++-- freqtrade/strategy/interface.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/includes/protections.md b/docs/includes/protections.md index a242a6256..bb4a7eb35 100644 --- a/docs/includes/protections.md +++ b/docs/includes/protections.md @@ -48,7 +48,7 @@ If `trade_limit` or more trades resulted in stoploss, trading will stop for `sto This applies across all pairs, unless `only_per_pair` is set to true, which will then only look at one pair at a time. -Similarly, this protection will by default look at all trades (long and short). For futures bots, setting `only_per_side` will make the bot only one side, and will then only lock this one side. +Similarly, this protection will by default look at all trades (long and short). For futures bots, setting `only_per_side` will make the bot only consider one side, and will then only lock this one side, allowing for example shorts to continue after a series of long stoplosses. The below example stops trading for all pairs for 4 candles after the last trade if the bot hit stoploss 4 times within the last 24 candles. @@ -62,7 +62,7 @@ def protections(self): "trade_limit": 4, "stop_duration_candles": 4, "only_per_pair": False, - "only_per_side": True + "only_per_side": False } ] ``` diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 7d16fc813..e37fddbe6 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -551,6 +551,7 @@ class IStrategy(ABC, HyperStrategyMixin): :param until: datetime in UTC until the pair should be blocked from opening new trades. Needs to be timezone aware `datetime.now(timezone.utc)` :param reason: Optional string explaining why the pair was locked. + :param side: Side to check, can be long, short or '*' """ PairLocks.lock_pair(pair, until, reason, side=side) From 4de0fdbfca2bb461b0c442661a7bc0624760494b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 24 Apr 2022 14:43:30 +0200 Subject: [PATCH 161/254] Minor edits found during review --- freqtrade/plugins/protections/max_drawdown_protection.py | 1 - freqtrade/plugins/protections/stoploss_guard.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index d759a23dd..7370b2b43 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -67,7 +67,6 @@ class MaxDrawdown(IProtection): f" within {self.lookback_period_str}.", logger.info) until = self.calculate_lock_end(trades, self._stop_duration) - # return True, until, self._reason(drawdown), None return ProtectionReturn( lock=True, until=until, diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py index 1943513ca..f9fe039d6 100644 --- a/freqtrade/plugins/protections/stoploss_guard.py +++ b/freqtrade/plugins/protections/stoploss_guard.py @@ -51,7 +51,7 @@ class StoplossGuard(IProtection): ExitType.STOPLOSS_ON_EXCHANGE.value) and trade.close_profit and trade.close_profit < 0)] - if self._only_per_side and side: + if self._only_per_side: # Long or short trades only trades = [trade for trade in trades if trade.trade_direction == side] From 086cc6be931faa996a1e139a0622fae9ea944e4a Mon Sep 17 00:00:00 2001 From: Nicolas Papp Date: Sun, 24 Apr 2022 17:37:09 -0300 Subject: [PATCH 162/254] Correction on tests --- freqtrade/data/btanalysis.py | 4 +--- tests/test_plotting.py | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 3803beb70..6911941e6 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -511,9 +511,7 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date' high_val = max_drawdown_df.loc[max_drawdown_df.iloc[:idxmin] ['high_value'].idxmax(), 'cumulative'] low_val = max_drawdown_df.loc[idxmin, 'cumulative'] - max_drawdown_rel = 0.0 - if starting_balance != 0: - max_drawdown_rel = max_drawdown_df.loc[idxmin, 'drawdown_relative'] + max_drawdown_rel = max_drawdown_df.loc[idxmin, 'drawdown_relative'] return ( abs(min(max_drawdown_df['drawdown'])), diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 940639465..ba602dd40 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -331,7 +331,13 @@ def test_generate_profit_graph(testdatadir): trades = trades[trades['pair'].isin(pairs)] - fig = generate_profit_graph(pairs, data, trades, timeframe="5m", stake_currency='BTC') + fig = generate_profit_graph( + pairs, + data, + trades, + timeframe="5m", + stake_currency='BTC', + starting_balance=0) assert isinstance(fig, go.Figure) assert fig.layout.title.text == "Freqtrade Profit plot" @@ -340,7 +346,7 @@ def test_generate_profit_graph(testdatadir): assert fig.layout.yaxis3.title.text == "Profit BTC" figure = fig.layout.figure - assert len(figure.data) == 7 + assert len(figure.data) == 8 avgclose = find_trace_in_fig_data(figure.data, "Avg close price") assert isinstance(avgclose, go.Scatter) @@ -355,6 +361,9 @@ def test_generate_profit_graph(testdatadir): underwater = find_trace_in_fig_data(figure.data, "Underwater Plot") assert isinstance(underwater, go.Scatter) + underwater_relative = find_trace_in_fig_data(figure.data, "Underwater Plot (%)") + assert isinstance(underwater_relative, go.Scatter) + for pair in pairs: profit_pair = find_trace_in_fig_data(figure.data, f"Profit {pair}") assert isinstance(profit_pair, go.Scatter) @@ -362,7 +371,7 @@ def test_generate_profit_graph(testdatadir): with pytest.raises(OperationalException, match=r"No trades found.*"): # Pair cannot be empty - so it's an empty dataframe. generate_profit_graph(pairs, data, trades.loc[trades['pair'].isnull()], timeframe="5m", - stake_currency='BTC') + stake_currency='BTC', starting_balance=0) def test_start_plot_dataframe(mocker): @@ -444,6 +453,7 @@ def test_plot_profit(default_conf, mocker, testdatadir): default_conf['datadir'] = testdatadir default_conf['exportfilename'] = testdatadir / 'backtest-result_test_nofile.json' default_conf['pairs'] = ['ETH/BTC', 'LTC/BTC'] + default_conf['available_capital'] = 1000 profit_mock = MagicMock() store_mock = MagicMock() From e8aec967ddb9ab7d33cee93254201c8330e41a92 Mon Sep 17 00:00:00 2001 From: Nicolas Papp Date: Sun, 24 Apr 2022 17:42:52 -0300 Subject: [PATCH 163/254] Update on note --- freqtrade/data/btanalysis.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 6911941e6..5f5ced053 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -446,7 +446,8 @@ def _calc_drawdown_series(profit_results: pd.DataFrame, *, date_col: str, value_ max_balance = starting_balance + max_drawdown_df['high_value'] max_drawdown_df['drawdown_relative'] = ((max_balance - cumulative_balance) / max_balance) else: - # This is not completely accurate + # NOTE: This is not completely accurate, + # but might good enough if starting_balance is not available max_drawdown_df['drawdown_relative'] = ( (max_drawdown_df['high_value'] - max_drawdown_df['cumulative']) / max_drawdown_df['high_value']) From 46ac46a5d3a9629b7f9d3ce8c704f693336b811f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 03:17:23 +0000 Subject: [PATCH 164/254] Bump ccxt from 1.79.81 to 1.80.61 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.79.81 to 1.80.61. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.79.81...1.80.61) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 571d1892c..de14b9f2c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.22.3 pandas==1.4.2 pandas-ta==0.3.14b -ccxt==1.79.81 +ccxt==1.80.61 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.2 aiohttp==3.8.1 From 5bfa2186a77f774cb3043edabafd56745dab3a53 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 03:17:29 +0000 Subject: [PATCH 165/254] Bump pytest from 7.1.1 to 7.1.2 Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.1.1 to 7.1.2. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.1.1...7.1.2) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 4fb4456f0..7f914875b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,7 +9,7 @@ flake8==4.0.1 flake8-tidy-imports==4.6.0 mypy==0.942 pre-commit==2.18.1 -pytest==7.1.1 +pytest==7.1.2 pytest-asyncio==0.18.3 pytest-cov==3.0.0 pytest-mock==3.7.0 From eee9fbb6690fa23749a4cf71940ec2e83bcb59c0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 03:17:38 +0000 Subject: [PATCH 166/254] Bump types-python-dateutil from 2.8.11 to 2.8.12 Bumps [types-python-dateutil](https://github.com/python/typeshed) from 2.8.11 to 2.8.12. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-python-dateutil dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 4fb4456f0..4f296b321 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -28,4 +28,4 @@ types-requests==2.27.19 types-tabulate==0.8.7 # Extensions to datetime library -types-python-dateutil==2.8.11 +types-python-dateutil==2.8.12 From 9b39c835867120a69041c36712700a45eab49694 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 03:17:44 +0000 Subject: [PATCH 167/254] Bump mkdocs-material from 8.2.9 to 8.2.10 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.2.9 to 8.2.10. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/8.2.9...8.2.10) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 8d03a38c3..c6b683e8f 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.3.0 -mkdocs-material==8.2.9 +mkdocs-material==8.2.10 mdx_truly_sane_lists==1.2 pymdown-extensions==9.3 jinja2==3.1.1 From 399be6f4e54566cf4e8e7d5d2f4719c9d0a9a765 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 03:17:53 +0000 Subject: [PATCH 168/254] Bump types-requests from 2.27.19 to 2.27.20 Bumps [types-requests](https://github.com/python/typeshed) from 2.27.19 to 2.27.20. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 4fb4456f0..be23d14bf 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -24,7 +24,7 @@ nbconvert==6.5.0 # mypy types types-cachetools==5.0.1 types-filelock==3.2.5 -types-requests==2.27.19 +types-requests==2.27.20 types-tabulate==0.8.7 # Extensions to datetime library From b4afbb0b0ac2bdb96a6b653bc44240c3e5669112 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 04:54:53 +0000 Subject: [PATCH 169/254] Bump pymdown-extensions from 9.3 to 9.4 Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 9.3 to 9.4. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/9.3...9.4) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index c6b683e8f..97be17243 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.3.0 mkdocs-material==8.2.10 mdx_truly_sane_lists==1.2 -pymdown-extensions==9.3 +pymdown-extensions==9.4 jinja2==3.1.1 From 562e36c3ec20ce5d05c44fac78eb7e2ac71fb7a8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 07:01:27 +0200 Subject: [PATCH 170/254] Remove Interface V1 support --- freqtrade/resolvers/strategy_resolver.py | 18 +++++++++------ freqtrade/strategy/interface.py | 28 ++++-------------------- 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 0265ad6c3..44d590b67 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -217,15 +217,19 @@ class StrategyResolver(IResolver): raise OperationalException( "`populate_exit_trend` or `populate_sell_trend` must be implemented.") - strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args) - strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args) - strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args) + _populate_fun_len = len(getfullargspec(strategy.populate_indicators).args) + _buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args) + _sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args) if any(x == 2 for x in [ - strategy._populate_fun_len, - strategy._buy_fun_len, - strategy._sell_fun_len + _populate_fun_len, + _buy_fun_len, + _sell_fun_len ]): - strategy.INTERFACE_VERSION = 1 + raise OperationalException( + "Strategy Interface v1 is no longer supported. " + "Please update your strategy to implement " + "`populate_indicators`, `populate_entry_trend` and `populate_exit_trend` " + "with the metadata argument. ") return strategy @staticmethod diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index afcc1aa99..0ec3895bc 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -3,7 +3,6 @@ IStrategy interface This module defines the interface to apply for strategies """ import logging -import warnings from abc import ABC, abstractmethod from datetime import datetime, timedelta, timezone from typing import Dict, List, Optional, Tuple, Union @@ -44,14 +43,11 @@ class IStrategy(ABC, HyperStrategyMixin): """ # Strategy interface version # Default to version 2 - # Version 1 is the initial interface without metadata dict + # Version 1 is the initial interface without metadata dict - deprecated and no longer supported. # Version 2 populate_* include metadata dict # Version 3 - First version with short and leverage support INTERFACE_VERSION: int = 3 - _populate_fun_len: int = 0 - _buy_fun_len: int = 0 - _sell_fun_len: int = 0 _ft_params_from_file: Dict # associated minimal roi minimal_roi: Dict = {} @@ -1090,12 +1086,7 @@ class IStrategy(ABC, HyperStrategyMixin): dataframe = _create_and_merge_informative_pair( self, dataframe, metadata, inf_data, populate_fn) - if self._populate_fun_len == 2: - warnings.warn("deprecated - check out the Sample strategy to see " - "the current function headers!", DeprecationWarning) - return self.populate_indicators(dataframe) # type: ignore - else: - return self.populate_indicators(dataframe, metadata) + return self.populate_indicators(dataframe, metadata) def advise_entry(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ @@ -1109,12 +1100,7 @@ class IStrategy(ABC, HyperStrategyMixin): logger.debug(f"Populating enter signals for pair {metadata.get('pair')}.") - if self._buy_fun_len == 2: - warnings.warn("deprecated - check out the Sample strategy to see " - "the current function headers!", DeprecationWarning) - df = self.populate_buy_trend(dataframe) # type: ignore - else: - df = self.populate_entry_trend(dataframe, metadata) + df = self.populate_entry_trend(dataframe, metadata) if 'enter_long' not in df.columns: df = df.rename({'buy': 'enter_long', 'buy_tag': 'enter_tag'}, axis='columns') @@ -1129,14 +1115,8 @@ class IStrategy(ABC, HyperStrategyMixin): currently traded pair :return: DataFrame with exit column """ - logger.debug(f"Populating exit signals for pair {metadata.get('pair')}.") - if self._sell_fun_len == 2: - warnings.warn("deprecated - check out the Sample strategy to see " - "the current function headers!", DeprecationWarning) - df = self.populate_sell_trend(dataframe) # type: ignore - else: - df = self.populate_exit_trend(dataframe, metadata) + df = self.populate_exit_trend(dataframe, metadata) if 'exit_long' not in df.columns: df = df.rename({'sell': 'exit_long'}, axis='columns') return df From ec2582a4aed614dd1da552e9b76562e09f41c3d8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 07:02:09 +0200 Subject: [PATCH 171/254] Update tests to no longer use Strategy V1 --- tests/optimize/test_backtesting.py | 27 ++++----- tests/strategy/strats/legacy_strategy_v1.py | 60 +----------------- tests/strategy/test_interface.py | 2 +- tests/strategy/test_strategy_loading.py | 67 ++------------------- 4 files changed, 21 insertions(+), 135 deletions(-) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 4d32a7516..d7ee4a042 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -500,7 +500,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti Backtesting(default_conf) # Multiple strategies - default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY, 'TestStrategyLegacyV1'] + default_conf['strategy_list'] = [CURRENT_TEST_STRATEGY, 'StrategyTestV2'] with pytest.raises(OperationalException, match='PrecisionFilter not allowed for backtesting multiple strategies.'): Backtesting(default_conf) @@ -1198,7 +1198,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): '--disable-max-market-positions', '--strategy-list', CURRENT_TEST_STRATEGY, - 'TestStrategyLegacyV1', + 'StrategyTestV2', ] args = get_args(args) start_backtesting(args) @@ -1221,14 +1221,13 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir): 'up to 2017-11-14 22:58:00 (0 days).', 'Parameter --enable-position-stacking detected ...', f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}', - 'Running backtesting for Strategy TestStrategyLegacyV1', + 'Running backtesting for Strategy StrategyTestV2', ] for line in exists: assert log_has(line, caplog) -@pytest.mark.filterwarnings("ignore:deprecated") def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdatadir, capsys): default_conf.update({ "use_exit_signal": True, @@ -1310,7 +1309,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat '--breakdown', 'day', '--strategy-list', CURRENT_TEST_STRATEGY, - 'TestStrategyLegacyV1', + 'StrategyTestV2', ] args = get_args(args) start_backtesting(args) @@ -1327,7 +1326,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat 'up to 2017-11-14 22:58:00 (0 days).', 'Parameter --enable-position-stacking detected ...', f'Running backtesting for Strategy {CURRENT_TEST_STRATEGY}', - 'Running backtesting for Strategy TestStrategyLegacyV1', + 'Running backtesting for Strategy StrategyTestV2', ] for line in exists: @@ -1592,7 +1591,7 @@ def test_backtest_start_multi_strat_caching(default_conf, mocker, caplog, testda min_backtest_date = now - timedelta(weeks=4) load_backtest_metadata = MagicMock(return_value={ 'StrategyTestV2': {'run_id': '1', 'backtest_start_time': now.timestamp()}, - 'TestStrategyLegacyV1': {'run_id': run_id, 'backtest_start_time': start_time.timestamp()} + 'StrategyTestV3': {'run_id': run_id, 'backtest_start_time': start_time.timestamp()} }) load_backtest_stats = MagicMock(side_effect=[ { @@ -1601,9 +1600,9 @@ def test_backtest_start_multi_strat_caching(default_conf, mocker, caplog, testda 'strategy_comparison': [{'key': 'StrategyTestV2'}] }, { - 'metadata': {'TestStrategyLegacyV1': {'run_id': '2'}}, - 'strategy': {'TestStrategyLegacyV1': {}}, - 'strategy_comparison': [{'key': 'TestStrategyLegacyV1'}] + 'metadata': {'StrategyTestV3': {'run_id': '2'}}, + 'strategy': {'StrategyTestV3': {}}, + 'strategy_comparison': [{'key': 'StrategyTestV3'}] } ]) mocker.patch('pathlib.Path.glob', return_value=[ @@ -1627,7 +1626,7 @@ def test_backtest_start_multi_strat_caching(default_conf, mocker, caplog, testda '--cache', cache, '--strategy-list', 'StrategyTestV2', - 'TestStrategyLegacyV1', + 'StrategyTestV3', ] args = get_args(args) start_backtesting(args) @@ -1649,7 +1648,7 @@ def test_backtest_start_multi_strat_caching(default_conf, mocker, caplog, testda assert backtestmock.call_count == 2 exists = [ 'Running backtesting for Strategy StrategyTestV2', - 'Running backtesting for Strategy TestStrategyLegacyV1', + 'Running backtesting for Strategy StrategyTestV3', 'Ignoring max_open_trades (--disable-max-market-positions was used) ...', 'Backtesting with data from 2017-11-14 21:17:00 up to 2017-11-14 22:58:00 (0 days).', ] @@ -1657,12 +1656,12 @@ def test_backtest_start_multi_strat_caching(default_conf, mocker, caplog, testda assert backtestmock.call_count == 0 exists = [ 'Reusing result of previous backtest for StrategyTestV2', - 'Reusing result of previous backtest for TestStrategyLegacyV1', + 'Reusing result of previous backtest for StrategyTestV3', ] else: exists = [ 'Reusing result of previous backtest for StrategyTestV2', - 'Running backtesting for Strategy TestStrategyLegacyV1', + 'Running backtesting for Strategy StrategyTestV3', 'Ignoring max_open_trades (--disable-max-market-positions was used) ...', 'Backtesting with data from 2017-11-14 21:17:00 up to 2017-11-14 22:58:00 (0 days).', ] diff --git a/tests/strategy/strats/legacy_strategy_v1.py b/tests/strategy/strats/legacy_strategy_v1.py index bad2aa40d..a1fe4ee15 100644 --- a/tests/strategy/strats/legacy_strategy_v1.py +++ b/tests/strategy/strats/legacy_strategy_v1.py @@ -1,85 +1,29 @@ - -# --- Do not remove these libs --- -# Add your lib to import here -import talib.abstract as ta from pandas import DataFrame from freqtrade.strategy import IStrategy -# -------------------------------- - -# This class is a sample. Feel free to customize it. +# Dummy strategy - no longer loads but raises an exception. class TestStrategyLegacyV1(IStrategy): - """ - This is a test strategy using the legacy function headers, which will be - removed in a future update. - Please do not use this as a template, but refer to user_data/strategy/sample_strategy.py - for a uptodate version of this template. - """ - # Minimal ROI designed for the strategy. - # This attribute will be overridden if the config file contains "minimal_roi" minimal_roi = { "40": 0.0, "30": 0.01, "20": 0.02, "0": 0.04 } - - # Optimal stoploss designed for the strategy - # This attribute will be overridden if the config file contains "stoploss" stoploss = -0.10 timeframe = '5m' def populate_indicators(self, dataframe: DataFrame) -> 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. - """ - - # Momentum Indicator - # ------------------------------------ - - # ADX - dataframe['adx'] = ta.ADX(dataframe) - - # TEMA - Triple Exponential Moving Average - dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) return dataframe def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: - """ - Based on TA indicators, populates the buy signal for the given dataframe - :param dataframe: DataFrame - :return: DataFrame with buy column - """ - dataframe.loc[ - ( - (dataframe['adx'] > 30) & - (dataframe['tema'] > dataframe['tema'].shift(1)) & - (dataframe['volume'] > 0) - ), - 'buy'] = 1 return dataframe def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: - """ - Based on TA indicators, populates the sell signal for the given dataframe - :param dataframe: DataFrame - :return: DataFrame with buy column - """ - dataframe.loc[ - ( - (dataframe['adx'] > 70) & - (dataframe['tema'] < dataframe['tema'].shift(1)) & - (dataframe['volume'] > 0) - ), - 'sell'] = 1 + return dataframe diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index a86d69135..ea81fe968 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -686,7 +686,7 @@ def test_is_pair_locked(default_conf): def test_is_informative_pairs_callback(default_conf): - default_conf.update({'strategy': 'TestStrategyLegacyV1'}) + default_conf.update({'strategy': 'StrategyTestV2'}) strategy = StrategyResolver.load_strategy(default_conf) # Should return empty # Uses fallback to base implementation diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index e74a2a022..85f7961ed 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -100,7 +100,7 @@ def test_load_strategy_noname(default_conf): @pytest.mark.filterwarnings("ignore:deprecated") -@pytest.mark.parametrize('strategy_name', ['StrategyTestV2', 'TestStrategyLegacyV1']) +@pytest.mark.parametrize('strategy_name', ['StrategyTestV2']) def test_strategy_pre_v3(result, default_conf, strategy_name): default_conf.update({'strategy': strategy_name}) @@ -346,40 +346,6 @@ def test_strategy_override_use_exit_profit_only(caplog, default_conf): assert log_has("Override strategy 'exit_profit_only' with value in config file: True.", caplog) -@pytest.mark.filterwarnings("ignore:deprecated") -def test_deprecate_populate_indicators(result, default_conf): - default_location = Path(__file__).parent / "strats" - default_conf.update({'strategy': 'TestStrategyLegacyV1', - 'strategy_path': default_location}) - strategy = StrategyResolver.load_strategy(default_conf) - with warnings.catch_warnings(record=True) as w: - # Cause all warnings to always be triggered. - warnings.simplefilter("always") - indicators = strategy.advise_indicators(result, {'pair': 'ETH/BTC'}) - assert len(w) == 1 - assert issubclass(w[-1].category, DeprecationWarning) - assert "deprecated - check out the Sample strategy to see the current function headers!" \ - in str(w[-1].message) - - with warnings.catch_warnings(record=True) as w: - # Cause all warnings to always be triggered. - warnings.simplefilter("always") - strategy.advise_entry(indicators, {'pair': 'ETH/BTC'}) - assert len(w) == 1 - assert issubclass(w[-1].category, DeprecationWarning) - assert "deprecated - check out the Sample strategy to see the current function headers!" \ - in str(w[-1].message) - - with warnings.catch_warnings(record=True) as w: - # Cause all warnings to always be triggered. - warnings.simplefilter("always") - strategy.advise_exit(indicators, {'pair': 'ETH_BTC'}) - assert len(w) == 1 - assert issubclass(w[-1].category, DeprecationWarning) - assert "deprecated - check out the Sample strategy to see the current function headers!" \ - in str(w[-1].message) - - @pytest.mark.filterwarnings("ignore:deprecated") def test_missing_implements(default_conf, caplog): @@ -438,33 +404,14 @@ def test_missing_implements(default_conf, caplog): StrategyResolver.load_strategy(default_conf) -@pytest.mark.filterwarnings("ignore:deprecated") -def test_call_deprecated_function(result, default_conf, caplog): +def test_call_deprecated_function(default_conf): default_location = Path(__file__).parent / "strats" del default_conf['timeframe'] default_conf.update({'strategy': 'TestStrategyLegacyV1', 'strategy_path': default_location}) - strategy = StrategyResolver.load_strategy(default_conf) - metadata = {'pair': 'ETH/BTC'} - - # Make sure we are using a legacy function - assert strategy._populate_fun_len == 2 - assert strategy._buy_fun_len == 2 - assert strategy._sell_fun_len == 2 - assert strategy.INTERFACE_VERSION == 1 - assert strategy.timeframe == '5m' - - indicator_df = strategy.advise_indicators(result, metadata=metadata) - assert isinstance(indicator_df, DataFrame) - assert 'adx' in indicator_df.columns - - enterdf = strategy.advise_entry(result, metadata=metadata) - assert isinstance(enterdf, DataFrame) - assert 'enter_long' in enterdf.columns - - exitdf = strategy.advise_exit(result, metadata=metadata) - assert isinstance(exitdf, DataFrame) - assert 'exit_long' in exitdf + with pytest.raises(OperationalException, + match=r"Strategy Interface v1 is no longer supported.*"): + StrategyResolver.load_strategy(default_conf) def test_strategy_interface_versioning(result, default_conf): @@ -472,10 +419,6 @@ def test_strategy_interface_versioning(result, default_conf): strategy = StrategyResolver.load_strategy(default_conf) metadata = {'pair': 'ETH/BTC'} - # Make sure we are using a legacy function - assert strategy._populate_fun_len == 3 - assert strategy._buy_fun_len == 3 - assert strategy._sell_fun_len == 3 assert strategy.INTERFACE_VERSION == 2 indicator_df = strategy.advise_indicators(result, metadata=metadata) From 9bb0f1f675d211a3dd95359b6129c10628dcc958 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 07:09:08 +0200 Subject: [PATCH 172/254] Move legacy strategy to "broken strats" folder --- tests/commands/test_commands.py | 16 ++++++++-------- tests/rpc/test_rpc_apiserver.py | 1 - .../{ => broken_strats}/legacy_strategy_v1.py | 1 + tests/strategy/test_strategy_loading.py | 9 ++++----- 4 files changed, 13 insertions(+), 14 deletions(-) rename tests/strategy/strats/{ => broken_strats}/legacy_strategy_v1.py (97%) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index d1f54ad52..37eeda86a 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -859,8 +859,8 @@ def test_start_list_strategies(capsys): # pargs['config'] = None start_list_strategies(pargs) captured = capsys.readouterr() - assert "TestStrategyLegacyV1" in captured.out - assert "legacy_strategy_v1.py" not in captured.out + assert "StrategyTestV2" in captured.out + assert "strategy_test_v2.py" not in captured.out assert CURRENT_TEST_STRATEGY in captured.out # Test regular output @@ -874,8 +874,8 @@ def test_start_list_strategies(capsys): # pargs['config'] = None start_list_strategies(pargs) captured = capsys.readouterr() - assert "TestStrategyLegacyV1" in captured.out - assert "legacy_strategy_v1.py" in captured.out + assert "StrategyTestV2" in captured.out + assert "strategy_test_v2.py" in captured.out assert CURRENT_TEST_STRATEGY in captured.out # Test color output @@ -888,8 +888,8 @@ def test_start_list_strategies(capsys): # pargs['config'] = None start_list_strategies(pargs) captured = capsys.readouterr() - assert "TestStrategyLegacyV1" in captured.out - assert "legacy_strategy_v1.py" in captured.out + assert "StrategyTestV2" in captured.out + assert "strategy_test_v2.py" in captured.out assert CURRENT_TEST_STRATEGY in captured.out assert "LOAD FAILED" in captured.out # Recursive @@ -907,8 +907,8 @@ def test_start_list_strategies(capsys): # pargs['config'] = None start_list_strategies(pargs) captured = capsys.readouterr() - assert "TestStrategyLegacyV1" in captured.out - assert "legacy_strategy_v1.py" in captured.out + assert "StrategyTestV2" in captured.out + assert "strategy_test_v2.py" in captured.out assert "StrategyTestV2" in captured.out assert "TestStrategyNoImplements" in captured.out assert str(Path("broken_strats/broken_futures_strategies.py")) in captured.out diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index af8361571..4910213b4 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1389,7 +1389,6 @@ def test_api_strategies(botclient): 'StrategyTestV2', 'StrategyTestV3', 'StrategyTestV3Futures', - 'TestStrategyLegacyV1', ]} diff --git a/tests/strategy/strats/legacy_strategy_v1.py b/tests/strategy/strats/broken_strats/legacy_strategy_v1.py similarity index 97% rename from tests/strategy/strats/legacy_strategy_v1.py rename to tests/strategy/strats/broken_strats/legacy_strategy_v1.py index a1fe4ee15..f3b8c2696 100644 --- a/tests/strategy/strats/legacy_strategy_v1.py +++ b/tests/strategy/strats/broken_strats/legacy_strategy_v1.py @@ -1,3 +1,4 @@ +# type: ignore from pandas import DataFrame from freqtrade.strategy import IStrategy diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index 85f7961ed..3ed1eb0ce 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -1,6 +1,5 @@ # pragma pylint: disable=missing-docstring, protected-access, C0103 import logging -import warnings from base64 import urlsafe_b64encode from pathlib import Path @@ -35,7 +34,7 @@ def test_search_all_strategies_no_failed(): directory = Path(__file__).parent / "strats" strategies = StrategyResolver.search_all_objects(directory, enum_failed=False) assert isinstance(strategies, list) - assert len(strategies) == 6 + assert len(strategies) == 5 assert isinstance(strategies[0], dict) @@ -43,10 +42,10 @@ def test_search_all_strategies_with_failed(): directory = Path(__file__).parent / "strats" strategies = StrategyResolver.search_all_objects(directory, enum_failed=True) assert isinstance(strategies, list) - assert len(strategies) == 7 + assert len(strategies) == 6 # with enum_failed=True search_all_objects() shall find 2 good strategies # and 1 which fails to load - assert len([x for x in strategies if x['class'] is not None]) == 6 + assert len([x for x in strategies if x['class'] is not None]) == 5 assert len([x for x in strategies if x['class'] is None]) == 1 @@ -405,7 +404,7 @@ def test_missing_implements(default_conf, caplog): def test_call_deprecated_function(default_conf): - default_location = Path(__file__).parent / "strats" + default_location = Path(__file__).parent / "strats/broken_strats/" del default_conf['timeframe'] default_conf.update({'strategy': 'TestStrategyLegacyV1', 'strategy_path': default_location}) From 9bc6bbe472f58bbec82d741ab916d66c52b2978a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 07:23:16 +0200 Subject: [PATCH 173/254] Improve test for max_drawdown calculations --- freqtrade/plot/plotting.py | 2 +- tests/data/test_btanalysis.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index ed403e09f..a273f5555 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -1,9 +1,9 @@ import logging from pathlib import Path from typing import Any, Dict, List, Optional -from numpy import number import pandas as pd +from numpy import number from freqtrade.configuration import TimeRange from freqtrade.data.btanalysis import (analyze_trade_parallelism, calculate_max_drawdown, diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index f4275edd9..118ea4ca7 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -362,3 +362,35 @@ def test_calculate_max_drawdown2(): df = DataFrame(zip(values[:5], dates[:5]), columns=['profit', 'open_date']) with pytest.raises(ValueError, match='No losing trade, therefore no drawdown.'): calculate_max_drawdown(df, date_col='open_date', value_col='profit') + + +@pytest.mark.parametrize('values,relative,result,result_rel', [ + ([0.0, -500.0, 500.0, 10000.0, -1000.0], False, 1000.0, 0.090909), + ([0.0, -500.0, 500.0, 10000.0, -1000.0], True, 1000.0, 0.5), + +]) +def test_calculate_max_drawdown_abs(values, relative, result, result_rel): + """ + Test case from issue https://github.com/freqtrade/freqtrade/issues/6655 + [1000, 500, 1000, 11000, 10000] # absolute results + [1000, 50%, 0%, 0%, ~9%] # Relative drawdowns + """ + + dates = [Arrow(2020, 1, 1).shift(days=i) for i in range(len(values))] + df = DataFrame(zip(values, dates), columns=['profit_abs', 'open_date']) + # sort by profit and reset index + df = df.sort_values('profit_abs').reset_index(drop=True) + df1 = df.copy() + drawdown, hdate, ldate, hval, lval, drawdown_rel = calculate_max_drawdown( + df, date_col='open_date', starting_balance=1000, relative=relative) + # Ensure df has not been altered. + assert df.equals(df1) + + assert isinstance(drawdown, float) + assert isinstance(drawdown_rel, float) + # High must be before low + assert hdate < ldate + # High value must be higher than low value + assert hval > lval + assert drawdown == result + assert pytest.approx(drawdown_rel) == result_rel From 5ff2261b7486df6d4a66bde3dec8dbed5a8c30a9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 07:32:32 +0200 Subject: [PATCH 174/254] Improve test to explicitly test for dates --- tests/data/test_btanalysis.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 118ea4ca7..2ffd57a54 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -364,20 +364,20 @@ def test_calculate_max_drawdown2(): calculate_max_drawdown(df, date_col='open_date', value_col='profit') -@pytest.mark.parametrize('values,relative,result,result_rel', [ - ([0.0, -500.0, 500.0, 10000.0, -1000.0], False, 1000.0, 0.090909), - ([0.0, -500.0, 500.0, 10000.0, -1000.0], True, 1000.0, 0.5), +@pytest.mark.parametrize('profits,relative,highd,lowd,result,result_rel', [ + ([0.0, -500.0, 500.0, 10000.0, -1000.0], False, 3, 4, 1000.0, 0.090909), + ([0.0, -500.0, 500.0, 10000.0, -1000.0], True, 0, 1, 1000.0, 0.5), ]) -def test_calculate_max_drawdown_abs(values, relative, result, result_rel): +def test_calculate_max_drawdown_abs(profits, relative, highd, lowd, result, result_rel): """ Test case from issue https://github.com/freqtrade/freqtrade/issues/6655 [1000, 500, 1000, 11000, 10000] # absolute results [1000, 50%, 0%, 0%, ~9%] # Relative drawdowns """ - - dates = [Arrow(2020, 1, 1).shift(days=i) for i in range(len(values))] - df = DataFrame(zip(values, dates), columns=['profit_abs', 'open_date']) + init_date = Arrow(2020, 1, 1) + dates = [init_date.shift(days=i) for i in range(len(profits))] + df = DataFrame(zip(profits, dates), columns=['profit_abs', 'open_date']) # sort by profit and reset index df = df.sort_values('profit_abs').reset_index(drop=True) df1 = df.copy() @@ -388,6 +388,9 @@ def test_calculate_max_drawdown_abs(values, relative, result, result_rel): assert isinstance(drawdown, float) assert isinstance(drawdown_rel, float) + assert hdate == init_date.shift(days=highd) + assert ldate == init_date.shift(days=lowd) + # High must be before low assert hdate < ldate # High value must be higher than low value From ad6e5c53120053556b4cd0e19581a853e3e02432 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 07:41:51 +0200 Subject: [PATCH 175/254] Test informative fallback again --- tests/strategy/strats/strategy_test_v2.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/strategy/strats/strategy_test_v2.py b/tests/strategy/strats/strategy_test_v2.py index 8996b227a..85ff856e1 100644 --- a/tests/strategy/strats/strategy_test_v2.py +++ b/tests/strategy/strats/strategy_test_v2.py @@ -56,19 +56,6 @@ class StrategyTestV2(IStrategy): # By default this strategy does not use Position Adjustments position_adjustment_enable = False - def informative_pairs(self): - """ - Define additional, informative pair/interval combinations to be cached from the exchange. - These pair/interval combinations are non-tradeable, unless they are part - of the whitelist as well. - For more information, please consult the documentation - :return: List of tuples in the format (pair, interval) - Sample: return [("ETH/USDT", "5m"), - ("BTC/USDT", "15m"), - ] - """ - return [] - def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Adds several different TA indicators to the given DataFrame From 86b3aac9ba8a0db45c986fa8ebda7bb19292fe33 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 08:38:02 +0200 Subject: [PATCH 176/254] Fix FTX not fetching the very latest data --- freqtrade/exchange/exchange.py | 9 ++++++--- freqtrade/exchange/ftx.py | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index ab063762b..2eb705b53 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -64,6 +64,7 @@ class Exchange: "ohlcv_params": {}, "ohlcv_candle_limit": 500, "ohlcv_partial_candle": True, + "ohlcv_require_since": False, # Check https://github.com/ccxt/ccxt/issues/10767 for removal of ohlcv_volume_currency "ohlcv_volume_currency": "base", # "base" or "quote" "tickers_have_quoteVolume": True, @@ -1710,7 +1711,8 @@ class Exchange: def _build_coroutine(self, pair: str, timeframe: str, candle_type: CandleType, since_ms: Optional[int]) -> Coroutine: - if not since_ms and self.required_candle_call_count > 1: + if (not since_ms + and (self._ft_has["ohlcv_require_since"] or self.required_candle_call_count > 1)): # Multiple calls for one pair - to get more history one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe) move_to = one_call * self.required_candle_call_count @@ -1829,17 +1831,18 @@ class Exchange: pair, timeframe, since_ms, s ) params = deepcopy(self._ft_has.get('ohlcv_params', {})) + candle_limit = self.ohlcv_candle_limit(timeframe) if candle_type != CandleType.SPOT: params.update({'price': candle_type}) if candle_type != CandleType.FUNDING_RATE: data = await self._api_async.fetch_ohlcv( pair, timeframe=timeframe, since=since_ms, - limit=self.ohlcv_candle_limit(timeframe), params=params) + limit=candle_limit, params=params) else: # Funding rate data = await self._api_async.fetch_funding_rate_history( pair, since=since_ms, - limit=self.ohlcv_candle_limit(timeframe)) + limit=candle_limit) # Convert funding rate to candle pattern data = [[x['timestamp'], x['fundingRate'], 0, 0, 0, 0] for x in data] # Some exchanges sort OHLCV in ASC order and others in DESC. diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index f20aab138..d2dcf84a6 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -20,6 +20,7 @@ class Ftx(Exchange): _ft_has: Dict = { "stoploss_on_exchange": True, "ohlcv_candle_limit": 1500, + "ohlcv_require_since": True, "ohlcv_volume_currency": "quote", "mark_ohlcv_price": "index", "mark_ohlcv_timeframe": "1h", From 7b02114ad2826bf4260ec98d47d9cd9e9a191d78 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 09:49:51 +0200 Subject: [PATCH 177/254] Restrict trading pairs with too low precision closes #6606 --- freqtrade/exchange/exchange.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 2eb705b53..d4741bd64 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -369,6 +369,9 @@ class Exchange: return ( market.get('quote', None) is not None and market.get('base', None) is not None + and (self.precisionMode != TICK_SIZE + # Too low precision will falsify calculations + or market.get('precision', {}).get('price', None) > 1e-11) and ((self.trading_mode == TradingMode.SPOT and self.market_is_spot(market)) or (self.trading_mode == TradingMode.MARGIN and self.market_is_margin(market)) or (self.trading_mode == TradingMode.FUTURES and self.market_is_future(market))) From 6d576bc02d4474b29de678052b3a420d2aa2cc4d Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 10:15:07 +0200 Subject: [PATCH 178/254] Check pre-commit verison updates --- .github/workflows/ci.yml | 21 ++++++++++++--- .pre-commit-config.yaml | 7 +++++ build_helpers/pre_commit_update.py | 42 ++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 build_helpers/pre_commit_update.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1902a6c45..35c237837 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -265,6 +265,21 @@ jobs: details: Test Failed webhookUrl: ${{ secrets.DISCORD_WEBHOOK }} + mypy_version_check: + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: 3.9 + + - name: pre-commit dependencies + run: | + pip install pyaml + python build_helpers/pre_commit_update.py + docs_check: runs-on: ubuntu-20.04 steps: @@ -277,7 +292,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v3 with: - python-version: 3.8 + python-version: 3.9 - name: Documentation build run: | @@ -304,7 +319,7 @@ jobs: # Notify only once - when CI completes (and after deploy) in case it's successfull notify-complete: - needs: [ build_linux, build_macos, build_windows, docs_check ] + needs: [ build_linux, build_macos, build_windows, docs_check, mypy_version_check ] runs-on: ubuntu-20.04 steps: @@ -325,7 +340,7 @@ jobs: webhookUrl: ${{ secrets.DISCORD_WEBHOOK }} deploy: - needs: [ build_linux, build_macos, build_windows, docs_check ] + needs: [ build_linux, build_macos, build_windows, docs_check, mypy_version_check ] runs-on: ubuntu-20.04 if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 316baf0e3..f223f0b9b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,6 +11,13 @@ repos: rev: "v0.942" hooks: - id: mypy + args: [ freqtrade ] + additional_dependencies: + - types-cachetools==5.0.1 + - types-filelock==3.2.5 + - types-requests==2.27.19 + - types-tabulate==0.8.7 + - types-python-dateutil==2.8.11 # stages: [push] - repo: https://github.com/pycqa/isort diff --git a/build_helpers/pre_commit_update.py b/build_helpers/pre_commit_update.py new file mode 100644 index 000000000..8724d8ade --- /dev/null +++ b/build_helpers/pre_commit_update.py @@ -0,0 +1,42 @@ +# File used in CI to ensure pre-commit dependencies are kept uptodate. + +import sys +from pathlib import Path + +import yaml + + +pre_commit_file = Path('.pre-commit-config.yaml') +require_dev = Path('requirements-dev.txt') + +with require_dev.open('r') as rfile: + requirements = rfile.readlines() + +# Extract types only +type_reqs = [r.strip('\n') for r in requirements if r.startswith('types-')] + +with pre_commit_file.open('r') as file: + f = yaml.load(file, Loader=yaml.FullLoader) + + +mypy_repo = [repo for repo in f['repos'] if repo['repo'] + == 'https://github.com/pre-commit/mirrors-mypy'] + +hooks = mypy_repo[0]['hooks'][0]['additional_dependencies'] + +errors = [] +for hook in hooks: + if hook not in type_reqs: + errors.append(f"{hook} is missing in requirements-dev.txt.") + +for req in type_reqs: + if req not in hooks: + errors.append(f"{req} is missing in pre-config file.") + + +if errors: + for e in errors: + print(e) + sys.exit(1) + +sys.exit(0) From fc118d0e95d38e7f5a655c79fc3de41ab9f2f537 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 10:19:31 +0200 Subject: [PATCH 179/254] Re-align dependencies --- .pre-commit-config.yaml | 4 ++-- requirements-dev.txt | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f223f0b9b..0dd343bb8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,9 +15,9 @@ repos: additional_dependencies: - types-cachetools==5.0.1 - types-filelock==3.2.5 - - types-requests==2.27.19 + - types-requests==2.27.20 - types-tabulate==0.8.7 - - types-python-dateutil==2.8.11 + - types-python-dateutil==2.8.12 # stages: [push] - repo: https://github.com/pycqa/isort diff --git a/requirements-dev.txt b/requirements-dev.txt index b0210d64a..c4fe366a5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -26,6 +26,4 @@ types-cachetools==5.0.1 types-filelock==3.2.5 types-requests==2.27.20 types-tabulate==0.8.7 - -# Extensions to datetime library types-python-dateutil==2.8.12 From 4143ebbeae53eb27706cf15573f4f36b905ac8e0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 10:51:11 +0200 Subject: [PATCH 180/254] Add CAGR calculation to backtesting --- docs/backtesting.md | 2 ++ freqtrade/data/btanalysis.py | 11 +++++++++++ freqtrade/optimize/optimize_reports.py | 4 +++- tests/data/test_btanalysis.py | 27 +++++++++++++++++++------- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index f732068f1..a0a304400 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -299,6 +299,7 @@ A backtesting result will look like that: | Final balance | 0.01762792 BTC | | Absolute profit | 0.00762792 BTC | | Total profit % | 76.2% | +| CAGR % | 460.87% | | Trades per day | 3.575 | | Avg. stake amount | 0.001 BTC | | Total trade volume | 0.429 BTC | @@ -388,6 +389,7 @@ It contains some useful key metrics about performance of your strategy on backte | Final balance | 0.01762792 BTC | | Absolute profit | 0.00762792 BTC | | Total profit % | 76.2% | +| CAGR % | 460.87% | | Avg. stake amount | 0.001 BTC | | Total trade volume | 0.429 BTC | | | | diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 8abcc6747..206a6f5f3 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -553,3 +553,14 @@ def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> Tuple[f csum_max = csum_df['sum'].max() + starting_balance return csum_min, csum_max + + +def calculate_cagr(days_passed: int, starting_balance: float, final_balance: float) -> float: + """ + Calculate CAGR + :param days_passed: Days passed between start and ending balance + :param starting_balance: Starting balance + :param final_balance: Final balance to calculate CAGR against + :return: CAGR + """ + return (final_balance / starting_balance) ** (1 / (days_passed / 365)) - 1 diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 0ceb3a411..e8bd035d1 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -9,7 +9,7 @@ from pandas import DataFrame, to_datetime from tabulate import tabulate from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN, UNLIMITED_STAKE_AMOUNT -from freqtrade.data.btanalysis import (calculate_csum, calculate_market_change, +from freqtrade.data.btanalysis import (calculate_cagr, calculate_csum, calculate_market_change, calculate_max_drawdown) from freqtrade.misc import (decimals_per_coin, file_dump_joblib, file_dump_json, get_backtest_metadata_filename, round_coin_value) @@ -446,6 +446,7 @@ def generate_strategy_stats(pairlist: List[str], 'profit_total_abs': results['profit_abs'].sum(), 'profit_total_long_abs': results.loc[~results['is_short'], 'profit_abs'].sum(), 'profit_total_short_abs': results.loc[results['is_short'], 'profit_abs'].sum(), + 'cagr': calculate_cagr(backtest_days, start_balance, content['final_balance']), 'backtest_start': min_date.strftime(DATETIME_PRINT_FORMAT), 'backtest_start_ts': int(min_date.timestamp() * 1000), 'backtest_end': max_date.strftime(DATETIME_PRINT_FORMAT), @@ -746,6 +747,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: ('Absolute profit ', round_coin_value(strat_results['profit_total_abs'], strat_results['stake_currency'])), ('Total profit %', f"{strat_results['profit_total']:.2%}"), + ('CAGR %', f"{strat_results['cagr']:.2%}"), ('Trades per day', strat_results['trades_per_day']), ('Avg. daily profit %', f"{(strat_results['profit_total'] / strat_results['backtest_days']):.2%}"), diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 2b53e4900..eaf703b2d 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -8,13 +8,13 @@ from pandas import DataFrame, DateOffset, Timestamp, to_datetime from freqtrade.configuration import TimeRange from freqtrade.constants import LAST_BT_RESULT_FN -from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelism, calculate_csum, - calculate_market_change, calculate_max_drawdown, - calculate_underwater, combine_dataframes_with_mean, - create_cum_profit, extract_trades_of_period, - get_latest_backtest_filename, get_latest_hyperopt_file, - load_backtest_data, load_backtest_metadata, load_trades, - load_trades_from_db) +from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelism, calculate_cagr, + calculate_csum, calculate_market_change, + calculate_max_drawdown, calculate_underwater, + combine_dataframes_with_mean, create_cum_profit, + extract_trades_of_period, get_latest_backtest_filename, + get_latest_hyperopt_file, load_backtest_data, + load_backtest_metadata, load_trades, load_trades_from_db) from freqtrade.data.history import load_data, load_pair_history from freqtrade.exceptions import OperationalException from tests.conftest import CURRENT_TEST_STRATEGY, create_mock_trades @@ -336,6 +336,19 @@ def test_calculate_csum(testdatadir): csum_min, csum_max = calculate_csum(DataFrame()) +@pytest.mark.parametrize('start,end,days, expected', [ + (64900, 176000, 3 * 365, 0.3945), + (64900, 176000, 365, 1.7119), + (1000, 1000, 365, 0.0), + (1000, 1500, 365, 0.5), + (1000, 1500, 100, 3.3927), # sub year + (0.01000000, 0.01762792, 120, 4.6087), # sub year BTC values +]) +def test_calculate_cagr(start, end, days, expected): + + assert round(calculate_cagr(days, start, end), 4) == expected + + def test_calculate_max_drawdown2(): values = [0.011580, 0.010048, 0.011340, 0.012161, 0.010416, 0.010009, 0.020024, -0.024662, -0.022350, 0.020496, -0.029859, -0.030511, 0.010041, 0.010872, From 500fdc2759639d9275ab82bfbcaaa01e48017eae Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 11:12:35 +0200 Subject: [PATCH 181/254] run mypy also against tests --- .github/workflows/ci.yml | 4 ++-- .pre-commit-config.yaml | 2 +- freqtrade/strategy/informative_decorator.py | 2 +- freqtrade/strategy/interface.py | 2 +- setup.cfg | 4 ++++ 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35c237837..5bafe9cb8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,7 +100,7 @@ jobs: - name: Mypy run: | - mypy freqtrade scripts + mypy freqtrade scripts tests - name: Discord notification uses: rjstone/discord-webhook-notify@v1 @@ -255,7 +255,7 @@ jobs: - name: Mypy run: | - mypy freqtrade scripts + mypy freqtrade scripts tests - name: Discord notification uses: rjstone/discord-webhook-notify@v1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0dd343bb8..d980fc4e9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: rev: "v0.942" hooks: - id: mypy - args: [ freqtrade ] + args: [ freqtrade, scripts, tests ] additional_dependencies: - types-cachetools==5.0.1 - types-filelock==3.2.5 diff --git a/freqtrade/strategy/informative_decorator.py b/freqtrade/strategy/informative_decorator.py index 0dd5320cd..7dfdf5a8c 100644 --- a/freqtrade/strategy/informative_decorator.py +++ b/freqtrade/strategy/informative_decorator.py @@ -23,7 +23,7 @@ class InformativeData: def informative(timeframe: str, asset: str = '', fmt: Optional[Union[str, Callable[[Any], str]]] = None, *, - candle_type: Optional[CandleType] = None, + candle_type: Optional[Union[CandleType, str]] = None, ffill: bool = True) -> Callable[[PopulateIndicators], PopulateIndicators]: """ A decorator for populate_indicators_Nn(self, dataframe, metadata), allowing these functions to diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 0ec3895bc..300010b83 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -110,7 +110,7 @@ class IStrategy(ABC, HyperStrategyMixin): # Class level variables (intentional) containing # the dataprovider (dp) (access to other candles, historic data, ...) # and wallets - access to the current balance. - dp: Optional[DataProvider] + dp: DataProvider wallets: Optional[Wallets] = None # Filled from configuration stake_currency: str diff --git a/setup.cfg b/setup.cfg index a33ceda1f..edbd320c3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -53,6 +53,10 @@ exclude = [mypy] ignore_missing_imports = True warn_unused_ignores = True +exclude = (?x)( + ^build_helpers\.py$ + ) + [mypy-tests.*] ignore_errors = True From 2b3f68396081a41861797ef0a4abc3c08221765f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 11:23:45 +0200 Subject: [PATCH 182/254] Update pre-commit to exclude build-helpers --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d980fc4e9..2170b704a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ repos: rev: "v0.942" hooks: - id: mypy - args: [ freqtrade, scripts, tests ] + exclude: build_helpers additional_dependencies: - types-cachetools==5.0.1 - types-filelock==3.2.5 From 580da21ddaa3e9fa8a0016434bc0ffbaf9794276 Mon Sep 17 00:00:00 2001 From: froggleston Date: Mon, 25 Apr 2022 10:31:19 +0100 Subject: [PATCH 183/254] Move df append to pd concat --- freqtrade/optimize/backtesting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) mode change 100644 => 100755 freqtrade/optimize/backtesting.py diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py old mode 100644 new mode 100755 index 5442e425b..217e6ff54 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -10,6 +10,7 @@ from datetime import datetime, timedelta, timezone from typing import Any, Dict, List, Optional, Tuple from numpy import nan +import pandas as pd from pandas import DataFrame from freqtrade import constants @@ -1093,7 +1094,7 @@ class Backtesting: for t, v in pairresults.open_date.items(): allinds = pairdf.loc[(pairdf['date'] < v)] signal_inds = allinds.iloc[[-1]] - signal_candles_only_df = signal_candles_only_df.append(signal_inds) + signal_candles_only_df = pd.concat([signal_candles_only_df, signal_inds]) signal_candles_only[pair] = signal_candles_only_df From 4444259078c92ae1ff8adab487828d478bf0fcba Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 11:33:18 +0200 Subject: [PATCH 184/254] Fix hyperopt-loss interface to enforce kwargs --- freqtrade/optimize/hyperopt_loss_interface.py | 4 ++-- freqtrade/plot/plotting.py | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/freqtrade/optimize/hyperopt_loss_interface.py b/freqtrade/optimize/hyperopt_loss_interface.py index ac8239b75..8366dcc4f 100644 --- a/freqtrade/optimize/hyperopt_loss_interface.py +++ b/freqtrade/optimize/hyperopt_loss_interface.py @@ -19,11 +19,11 @@ class IHyperOptLoss(ABC): @staticmethod @abstractmethod - def hyperopt_loss_function(results: DataFrame, trade_count: int, + def hyperopt_loss_function(*, results: DataFrame, trade_count: int, min_date: datetime, max_date: datetime, config: Dict, processed: Dict[str, DataFrame], backtest_stats: Dict[str, Any], - *args, **kwargs) -> float: + **kwargs) -> float: """ Objective function, returns smaller number for better results """ diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index a273f5555..0edfd9caf 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -3,7 +3,6 @@ from pathlib import Path from typing import Any, Dict, List, Optional import pandas as pd -from numpy import number from freqtrade.configuration import TimeRange from freqtrade.data.btanalysis import (analyze_trade_parallelism, calculate_max_drawdown, @@ -159,7 +158,7 @@ def add_profit(fig, row, data: pd.DataFrame, column: str, name: str) -> make_sub def add_max_drawdown(fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame, - timeframe: str, starting_balance: number) -> make_subplots: + timeframe: str, starting_balance: float) -> make_subplots: """ Add scatter points indicating max drawdown """ @@ -192,7 +191,7 @@ def add_max_drawdown(fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame, return fig -def add_underwater(fig, row, trades: pd.DataFrame, starting_balance: number) -> make_subplots: +def add_underwater(fig, row, trades: pd.DataFrame, starting_balance: float) -> make_subplots: """ Add underwater plots """ @@ -526,7 +525,7 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame], trades: pd.DataFrame, timeframe: str, stake_currency: str, - starting_balance: number) -> go.Figure: + starting_balance: float) -> go.Figure: # Combine close-values for all pairs, rename columns to "pair" try: df_comb = combine_dataframes_with_mean(data, "close") From 431c539cbdf3e207d9a7afdc299a14f3b245f547 Mon Sep 17 00:00:00 2001 From: froggleston Date: Mon, 25 Apr 2022 10:42:24 +0100 Subject: [PATCH 185/254] Fix isort import order --- freqtrade/optimize/backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 217e6ff54..27e14ba93 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -9,8 +9,8 @@ from copy import deepcopy from datetime import datetime, timedelta, timezone from typing import Any, Dict, List, Optional, Tuple -from numpy import nan import pandas as pd +from numpy import nan from pandas import DataFrame from freqtrade import constants From 44000ae0b390ce4a4fa48e4fc588863fdafe8c87 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 25 Apr 2022 17:37:25 +0200 Subject: [PATCH 186/254] Fix CAGR missing for old results --- freqtrade/optimize/optimize_reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index e8bd035d1..dd058aff4 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -747,7 +747,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: ('Absolute profit ', round_coin_value(strat_results['profit_total_abs'], strat_results['stake_currency'])), ('Total profit %', f"{strat_results['profit_total']:.2%}"), - ('CAGR %', f"{strat_results['cagr']:.2%}"), + ('CAGR %', f"{strat_results['cagr']:.2%}" if 'cagr' in strat_results else 'N/A'), ('Trades per day', strat_results['trades_per_day']), ('Avg. daily profit %', f"{(strat_results['profit_total'] / strat_results['backtest_days']):.2%}"), From 0b90e1d30921b8cec6e97e612e44e51ea225b0c8 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 1 Apr 2022 08:39:56 -0600 Subject: [PATCH 187/254] Added bot_start callback to strategy interface --- freqtrade/freqtradebot.py | 2 ++ freqtrade/strategy/interface.py | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 57d7cac3c..bee6c4746 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -122,6 +122,8 @@ class FreqtradeBot(LoggingMixin): self._schedule.every().day.at(t).do(update) self.last_process = datetime(1970, 1, 1, tzinfo=timezone.utc) + self.strategy.bot_start() + def notify_status(self, msg: str) -> None: """ Public method for users of this class (worker, etc.) to send notifications diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 300010b83..40744ed08 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -193,6 +193,13 @@ class IStrategy(ABC, HyperStrategyMixin): """ return self.populate_sell_trend(dataframe, metadata) + def bot_start(self, **kwargs) -> None: + """ + Called only once after bot instantiation. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + """ + pass + def bot_loop_start(self, **kwargs) -> None: """ Called at the start of the bot iteration (one loop). From bf7da35e31a3d9a98fff35cd098ecaf83f6d91f2 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 1 Apr 2022 09:25:03 -0600 Subject: [PATCH 188/254] strategy callback on_whitelist_update --- freqtrade/freqtradebot.py | 4 ++++ freqtrade/strategy/interface.py | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index bee6c4746..949d821b3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -179,6 +179,7 @@ class FreqtradeBot(LoggingMixin): # Query trades from persistence layer trades = Trade.get_open_trades() + current_pair_whitelist = self.active_pair_whitelist self.active_pair_whitelist = self._refresh_active_whitelist(trades) # Refreshing candles @@ -214,6 +215,9 @@ class FreqtradeBot(LoggingMixin): Trade.commit() self.last_process = datetime.now(timezone.utc) + if current_pair_whitelist != self.active_pair_whitelist: + self.strategy.on_whitelist_update() + def process_stopped(self) -> None: """ Close all orders that were left open diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 40744ed08..00d18fa7b 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -209,6 +209,13 @@ class IStrategy(ABC, HyperStrategyMixin): """ pass + def on_whitelist_update(self, **kwargs) -> None: + """ + Called every time the whitelist updates + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + """ + pass + def check_buy_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ From d92761b2b168a19eea764864c5fb9612a379c510 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 3 Apr 2022 02:53:44 -0600 Subject: [PATCH 189/254] Revert "strategy callback on_whitelist_update" This reverts commit 39798dc1192161c3060830dd4684571aa86b7821. --- freqtrade/freqtradebot.py | 4 ---- freqtrade/strategy/interface.py | 7 ------- 2 files changed, 11 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 949d821b3..bee6c4746 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -179,7 +179,6 @@ class FreqtradeBot(LoggingMixin): # Query trades from persistence layer trades = Trade.get_open_trades() - current_pair_whitelist = self.active_pair_whitelist self.active_pair_whitelist = self._refresh_active_whitelist(trades) # Refreshing candles @@ -215,9 +214,6 @@ class FreqtradeBot(LoggingMixin): Trade.commit() self.last_process = datetime.now(timezone.utc) - if current_pair_whitelist != self.active_pair_whitelist: - self.strategy.on_whitelist_update() - def process_stopped(self) -> None: """ Close all orders that were left open diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 00d18fa7b..40744ed08 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -209,13 +209,6 @@ class IStrategy(ABC, HyperStrategyMixin): """ pass - def on_whitelist_update(self, **kwargs) -> None: - """ - Called every time the whitelist updates - :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. - """ - pass - def check_buy_timeout(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> bool: """ From 4fd904e0a91ecf03020060c3d718382775bd143b Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 3 Apr 2022 10:28:09 -0600 Subject: [PATCH 190/254] added bot_start to backtesting --- freqtrade/optimize/backtesting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 27e14ba93..1789290bc 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -182,6 +182,7 @@ class Backtesting: # since a "perfect" stoploss-exit is assumed anyway # And the regular "stoploss" function would not apply to that case self.strategy.order_types['stoploss_on_exchange'] = False + self.strategy.bot_start() def _load_protections(self, strategy: IStrategy): if self.config.get('enable_protections', False): From e09b4498fa419f0088b38cec2cef6c78307ee2e6 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 25 Apr 2022 17:30:49 -0600 Subject: [PATCH 191/254] added bot_start call to plot/plotting --- freqtrade/plot/plotting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 747248be7..5337016f3 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -610,6 +610,7 @@ def load_and_plot_trades(config: Dict[str, Any]): exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config) IStrategy.dp = DataProvider(config, exchange) + strategy.bot_start() plot_elements = init_plotscript(config, list(exchange.markets), strategy.startup_candle_count) timerange = plot_elements['timerange'] trades = plot_elements['trades'] From e76c6e8ad31a525802ec35fa0dd8c6e264d32328 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 25 Apr 2022 17:32:48 -0600 Subject: [PATCH 192/254] added bot_start call to edge_positioning.__init__ --- freqtrade/edge/edge_positioning.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 2fe41a17b..5dc84f172 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -90,6 +90,8 @@ class Edge: self.config['exchange']['pair_whitelist'], list(self.exchange.markets))[0]) except IndexError: self.fee = None + + strategy.bot_start() def calculate(self, pairs: List[str]) -> bool: if self.fee is None and pairs: From 810e190e164be61a530e4d550c246dddbcdb4f1c Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 25 Apr 2022 17:46:40 -0600 Subject: [PATCH 193/254] added tests for bot_start --- tests/optimize/test_backtesting.py | 1 + tests/optimize/test_edge_cli.py | 1 + tests/strategy/strats/strategy_test_v3.py | 5 +++++ tests/strategy/test_default_strategy.py | 1 + tests/test_plotting.py | 1 + 5 files changed, 9 insertions(+) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index d7ee4a042..16d1aa2a5 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -312,6 +312,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None: get_fee.assert_called() assert backtesting.fee == 0.5 assert not backtesting.strategy.order_types["stoploss_on_exchange"] + assert backtesting.strategy.bot_started is True def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index f0f436a43..d9711b318 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -94,6 +94,7 @@ def test_edge_init(mocker, edge_conf) -> None: assert edge_cli.config == edge_conf assert edge_cli.config['stake_amount'] == 'unlimited' assert callable(edge_cli.edge.calculate) + assert edge_conf['strategy'].bot_started is True def test_edge_init_fee(mocker, edge_conf) -> None: diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py index 372e29412..df83d3663 100644 --- a/tests/strategy/strats/strategy_test_v3.py +++ b/tests/strategy/strats/strategy_test_v3.py @@ -82,6 +82,11 @@ class StrategyTestV3(IStrategy): # }) # return prot + bot_started = False + + def bot_start(self): + self.bot_started = True + def informative_pairs(self): return [] diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py index 5cb8fce16..a60274afd 100644 --- a/tests/strategy/test_default_strategy.py +++ b/tests/strategy/test_default_strategy.py @@ -32,6 +32,7 @@ def test_strategy_test_v3(result, fee, is_short, side): assert type(indicators) is DataFrame assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame assert type(strategy.populate_sell_trend(indicators, metadata)) is DataFrame + assert strategy.bot_started is True trade = Trade( open_rate=19_000, diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 97f367608..f0a28c4eb 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -58,6 +58,7 @@ def test_init_plotscript(default_conf, mocker, testdatadir): assert "ohlcv" in ret assert "TRX/BTC" in ret["ohlcv"] assert "ADA/BTC" in ret["ohlcv"] + assert default_conf['strategy'].bot_started is True def test_add_indicators(default_conf, testdatadir, caplog): From 7f035a9d5384993dc195a0a5d8f2e22c5fc74d75 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 25 Apr 2022 17:59:50 -0600 Subject: [PATCH 194/254] added docs for bot_start --- docs/strategy-callbacks.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 7ec600a58..a53a6992c 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -7,6 +7,7 @@ Depending on the callback used, they may be called when entering / exiting a tra Currently available callbacks: +* [`bot_start()`](#bot-start) * [`bot_loop_start()`](#bot-loop-start) * [`custom_stake_amount()`](#stake-size-management) * [`custom_exit()`](#custom-exit-signal) @@ -21,6 +22,31 @@ Currently available callbacks: !!! Tip "Callback calling sequence" You can find the callback calling sequence in [bot-basics](bot-basics.md#bot-execution-logic) +## Bot start + +A simple callback which is called once when the bot starts. +This can be used to perform actions that must only be performed once and runs after dataprovider and wallet are set + +``` python +import asyncio + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + async def some_asynchronous_task(self): + self.dp['remote_data'] = requests.get('https://some_remote_source.example.com') + + def bot_start(self, **kwargs) -> None: + """ + Called only once after bot instantiation. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + """ + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + self.loop.run_until_complete(self.some_asynchronous_task()) + +``` ## Bot loop start A simple callback which is called once at the start of every bot throttling iteration (roughly every 5 seconds, unless configured differently). From 4cccf31a3eb005a2560a0479b42aa8ef2f4b5939 Mon Sep 17 00:00:00 2001 From: naveen <172697+naveensrinivasan@users.noreply.github.com> Date: Tue, 26 Apr 2022 01:07:59 +0000 Subject: [PATCH 195/254] chore: Set permissions for GitHub actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restrict the GitHub token permissions only to the required ones; this way, even if the attackers will succeed in compromising your workflow, they won’t be able to do much. - Included permissions for the action. https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/) Signed-off-by: naveen <172697+naveensrinivasan@users.noreply.github.com> --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5bafe9cb8..932649f61 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -309,6 +309,9 @@ jobs: webhookUrl: ${{ secrets.DISCORD_WEBHOOK }} cleanup-prior-runs: + permissions: + actions: write # for rokroskar/workflow-run-cleanup-action to obtain workflow name & cancel it + contents: read # for rokroskar/workflow-run-cleanup-action to obtain branch runs-on: ubuntu-20.04 steps: - name: Cleanup previous runs on this branch From bc5048e4f389ec535375c56f533cd81c24e6c948 Mon Sep 17 00:00:00 2001 From: Nicolas Papp Date: Mon, 25 Apr 2022 23:50:47 -0300 Subject: [PATCH 196/254] Update to backtesting.md --- docs/backtesting.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 5d836d01b..bc98e81bf 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -439,7 +439,9 @@ It contains some useful key metrics about performance of your strategy on backte - `Rejected Entry signals`: Trade entry signals that could not be acted upon due to `max_open_trades` being reached. - `Entry/Exit Timeouts`: Entry/exit orders which did not fill (only applicable if custom pricing is used). - `Min balance` / `Max balance`: Lowest and Highest Wallet balance during the backtest period. -- `Drawdown (Account)`: Maximum Account Drawdown experienced. Calculated as $(Absolute Drawdown) / (DrawdownHigh + startingBalance)$. +- `Max % of account underwater`: Maximum percentage your account has decreased from the top since the simulation started. +Calculated as the maximum of `(Max Balance - Current Balance) / (Max Balance)`. +- `Absolute Drawdown (Account)`: Maximum Account Drawdown experienced. Calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`. - `Drawdown`: Maximum, absolute drawdown experienced. Difference between Drawdown High and Subsequent Low point. - `Drawdown high` / `Drawdown low`: Profit at the beginning and end of the largest drawdown period. A negative low value means initial capital lost. - `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command). From a35dc843ea75fe8a4c5452ab8534b0c077bfc106 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Mon, 25 Apr 2022 22:39:58 -0600 Subject: [PATCH 197/254] removed asyncio from bot_start example --- docs/strategy-callbacks.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index a53a6992c..e0cfbe8c1 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -34,17 +34,12 @@ class AwesomeStrategy(IStrategy): # ... populate_* methods - async def some_asynchronous_task(self): - self.dp['remote_data'] = requests.get('https://some_remote_source.example.com') - def bot_start(self, **kwargs) -> None: """ Called only once after bot instantiation. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. """ - self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(self.loop) - self.loop.run_until_complete(self.some_asynchronous_task()) + self.dp['remote_data'] = requests.get('https://some_remote_source.example.com') ``` ## Bot loop start From 6d99222320f05de1f7c2607f0cbe1beb8a38c29f Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Tue, 26 Apr 2022 09:39:15 +0300 Subject: [PATCH 198/254] Add 'exit_tag' parameter to 'custom_exit_price' callback. --- docs/strategy-callbacks.md | 2 +- freqtrade/freqtradebot.py | 3 ++- freqtrade/optimize/backtesting.py | 3 ++- freqtrade/strategy/interface.py | 3 ++- .../templates/subtemplates/strategy_methods_advanced.j2 | 3 ++- tests/test_freqtradebot.py | 6 +++--- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 7ec600a58..005f94155 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -376,7 +376,7 @@ class AwesomeStrategy(IStrategy): def custom_exit_price(self, pair: str, trade: Trade, current_time: datetime, proposed_rate: float, - current_profit: float, **kwargs) -> float: + current_profit: float, exit_tag: Optional[str, **kwargs) -> float: dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 57d7cac3c..68623c748 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1373,7 +1373,8 @@ class FreqtradeBot(LoggingMixin): default_retval=proposed_limit_rate)( pair=trade.pair, trade=trade, current_time=datetime.now(timezone.utc), - proposed_rate=proposed_limit_rate, current_profit=current_profit) + proposed_rate=proposed_limit_rate, current_profit=current_profit, + exit_tag=exit_check.exit_reason) limit = self.get_valid_price(custom_exit_price, proposed_limit_rate) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 27e14ba93..21e124e72 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -540,7 +540,8 @@ class Backtesting: default_retval=closerate)( pair=trade.pair, trade=trade, current_time=exit_candle_time, - proposed_rate=closerate, current_profit=current_profit) + proposed_rate=closerate, current_profit=current_profit, + exit_tag=exit_.exit_reason) # We can't place orders lower than current low. # freqtrade does not support this in live, and the order would fill immediately if trade.is_short: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 300010b83..4f9e91b56 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -355,7 +355,7 @@ class IStrategy(ABC, HyperStrategyMixin): def custom_exit_price(self, pair: str, trade: Trade, current_time: datetime, proposed_rate: float, - current_profit: float, **kwargs) -> float: + current_profit: float, exit_tag: Optional[str], **kwargs) -> float: """ Custom exit price logic, returning the new exit price. @@ -368,6 +368,7 @@ class IStrategy(ABC, HyperStrategyMixin): :param current_time: datetime object, containing the current datetime :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing. :param current_profit: Current profit (as ratio), calculated based on current_rate. + :param exit_tag: Exit reason. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: New exit price value if provided """ diff --git a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 index d5e2ea8ce..3fa36d506 100644 --- a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 +++ b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 @@ -32,7 +32,7 @@ def custom_entry_price(self, pair: str, current_time: 'datetime', proposed_rate: def custom_exit_price(self, pair: str, trade: 'Trade', current_time: 'datetime', proposed_rate: float, - current_profit: float, **kwargs) -> float: + current_profit: float, exit_tag: Optional[str, **kwargs) -> float: """ Custom exit price logic, returning the new exit price. @@ -45,6 +45,7 @@ def custom_exit_price(self, pair: str, trade: 'Trade', :param current_time: datetime object, containing the current datetime :param proposed_rate: Rate, calculated based on pricing settings in exit_pricing. :param current_profit: Current profit (as ratio), calculated based on current_rate. + :param exit_tag: Exit reason. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: New exit price value if provided """ diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 3737c7c05..84d3c3324 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3221,7 +3221,7 @@ def test_execute_trade_exit_custom_exit_price( freqtrade.execute_trade_exit( trade=trade, limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'], - exit_check=ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL) + exit_check=ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL, exit_reason='foo') ) # Sell price must be different to default bid price @@ -3249,8 +3249,8 @@ def test_execute_trade_exit_custom_exit_price( 'profit_ratio': profit_ratio, 'stake_currency': 'USDT', 'fiat_currency': 'USD', - 'sell_reason': ExitType.EXIT_SIGNAL.value, - 'exit_reason': ExitType.EXIT_SIGNAL.value, + 'sell_reason': 'foo', + 'exit_reason': 'foo', 'open_date': ANY, 'close_date': ANY, 'close_rate': ANY, From 108f11b1d796dc8a371250f2dbb028161bad5739 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Apr 2022 06:42:56 +0200 Subject: [PATCH 199/254] Fix docs typos --- docs/strategy-callbacks.md | 2 +- freqtrade/templates/subtemplates/strategy_methods_advanced.j2 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 005f94155..563b5a2cb 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -376,7 +376,7 @@ class AwesomeStrategy(IStrategy): def custom_exit_price(self, pair: str, trade: Trade, current_time: datetime, proposed_rate: float, - current_profit: float, exit_tag: Optional[str, **kwargs) -> float: + current_profit: float, exit_tag: Optional[str], **kwargs) -> float: dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) diff --git a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 index 3fa36d506..ed40ef509 100644 --- a/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 +++ b/freqtrade/templates/subtemplates/strategy_methods_advanced.j2 @@ -32,7 +32,7 @@ def custom_entry_price(self, pair: str, current_time: 'datetime', proposed_rate: def custom_exit_price(self, pair: str, trade: 'Trade', current_time: 'datetime', proposed_rate: float, - current_profit: float, exit_tag: Optional[str, **kwargs) -> float: + current_profit: float, exit_tag: Optional[str], **kwargs) -> float: """ Custom exit price logic, returning the new exit price. From ad7fbfab1bacbfaa3778595d2f073411286f9619 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Apr 2022 13:27:33 +0200 Subject: [PATCH 200/254] Slightly improved styling --- freqtrade/rpc/telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index c2531fec3..1a9be4503 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -943,7 +943,7 @@ class Telegram(RPCHandler): else: fiat_currency = self._config.get('fiat_display_currency', '') try: - statlist, head, fiat_profit_sum = self._rpc._rpc_status_table( + statlist, _, _ = self._rpc._rpc_status_table( self._config['stake_currency'], fiat_currency) except RPCException: self._send_msg(msg='No open trade found.') From 30c9dc697530fda7add40cf7cae941df4cb06076 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Apr 2022 13:53:11 +0200 Subject: [PATCH 201/254] Fix exit-signa being assigned when tag is set but no signal is present. --- freqtrade/optimize/backtesting.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 21e124e72..210eab39b 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -54,6 +54,11 @@ ESHORT_IDX = 8 # Exit short ENTER_TAG_IDX = 9 EXIT_TAG_IDX = 10 +# Every change to this headers list must evaluate further usages of the resulting tuple +# and eventually change the constants for indexes at the top +HEADERS = ['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long', + 'enter_short', 'exit_short', 'enter_tag', 'exit_tag'] + class Backtesting: """ @@ -305,10 +310,7 @@ class Backtesting: :param processed: a processed dictionary with format {pair, data}, which gets cleared to optimize memory usage! """ - # Every change to this headers list must evaluate further usages of the resulting tuple - # and eventually change the constants for indexes at the top - headers = ['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long', - 'enter_short', 'exit_short', 'enter_tag', 'exit_tag'] + data: Dict = {} self.progress.init_step(BacktestState.CONVERT, len(processed)) @@ -320,7 +322,7 @@ class Backtesting: if not pair_data.empty: # Cleanup from prior runs - pair_data.drop(headers[5:] + ['buy', 'sell'], axis=1, errors='ignore') + pair_data.drop(HEADERS[5:] + ['buy', 'sell'], axis=1, errors='ignore') df_analyzed = self.strategy.advise_exit( self.strategy.advise_entry(pair_data, {'pair': pair}), @@ -339,7 +341,7 @@ class Backtesting: # To avoid using data from future, we use entry/exit signals shifted # from the previous candle - for col in headers[5:]: + for col in HEADERS[5:]: tag_col = col in ('enter_tag', 'exit_tag') if col in df_analyzed.columns: df_analyzed.loc[:, col] = df_analyzed.loc[:, col].replace( @@ -351,7 +353,7 @@ class Backtesting: # Convert from Pandas to list for performance reasons # (Looping Pandas is slow.) - data[pair] = df_analyzed[headers].values.tolist() if not df_analyzed.empty else [] + data[pair] = df_analyzed[HEADERS].values.tolist() if not df_analyzed.empty else [] return data def _get_close_rate(self, row: Tuple, trade: LocalTrade, exit: ExitCheckTuple, @@ -515,10 +517,10 @@ class Backtesting: exit_candle_time: datetime = row[DATE_IDX].to_pydatetime() enter = row[SHORT_IDX] if trade.is_short else row[LONG_IDX] - exit_ = row[ESHORT_IDX] if trade.is_short else row[ELONG_IDX] + exit_sig = row[ESHORT_IDX] if trade.is_short else row[ELONG_IDX] exit_ = self.strategy.should_exit( trade, row[OPEN_IDX], exit_candle_time, # type: ignore - enter=enter, exit_=exit_, + enter=enter, exit_=exit_sig, low=row[LOW_IDX], high=row[HIGH_IDX] ) @@ -568,6 +570,7 @@ class Backtesting: len(row) > EXIT_TAG_IDX and row[EXIT_TAG_IDX] is not None and len(row[EXIT_TAG_IDX]) > 0 + and exit_.exit_type in (ExitType.EXIT_SIGNAL,) ): trade.exit_reason = row[EXIT_TAG_IDX] @@ -626,9 +629,7 @@ class Backtesting: detail_data.loc[:, 'exit_short'] = row[ESHORT_IDX] detail_data.loc[:, 'enter_tag'] = row[ENTER_TAG_IDX] detail_data.loc[:, 'exit_tag'] = row[EXIT_TAG_IDX] - headers = ['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long', - 'enter_short', 'exit_short', 'enter_tag', 'exit_tag'] - for det_row in detail_data[headers].values.tolist(): + for det_row in detail_data[HEADERS].values.tolist(): res = self._get_exit_trade_entry_for_candle(trade, det_row) if res: return res From 2c0a7c5d74df52a4ff166bc23515d318b5d08466 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Apr 2022 17:01:23 +0200 Subject: [PATCH 202/254] Don't call interest_rate and isolated_liq twice --- freqtrade/freqtradebot.py | 12 ------------ tests/test_freqtradebot.py | 12 +++++------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 68623c748..c82f2c8fe 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -664,16 +664,6 @@ class FreqtradeBot(LoggingMixin): amount = safe_value_fallback(order, 'filled', 'amount') enter_limit_filled_price = safe_value_fallback(order, 'average', 'price') - # TODO: this might be unnecessary, as we're calling it in update_trade_state. - isolated_liq = self.exchange.get_liquidation_price( - leverage=leverage, - pair=pair, - amount=amount, - open_rate=enter_limit_filled_price, - is_short=is_short - ) - interest_rate = self.exchange.get_interest_rate() - # Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker') base_currency = self.exchange.get_pair_base_currency(pair) @@ -702,8 +692,6 @@ class FreqtradeBot(LoggingMixin): timeframe=timeframe_to_minutes(self.config['timeframe']), leverage=leverage, is_short=is_short, - interest_rate=interest_rate, - liquidation_price=isolated_liq, trading_mode=self.trading_mode, funding_fees=funding_fees ) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 84d3c3324..89fe88a2c 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -717,12 +717,12 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker) (True, 'spot', 'gateio', None, 0.0, None), (False, 'spot', 'okx', None, 0.0, None), (True, 'spot', 'okx', None, 0.0, None), - (True, 'futures', 'binance', 'isolated', 0.0, 11.89108910891089), - (False, 'futures', 'binance', 'isolated', 0.0, 8.070707070707071), + (True, 'futures', 'binance', 'isolated', 0.0, 11.88151815181518), + (False, 'futures', 'binance', 'isolated', 0.0, 8.080471380471382), (True, 'futures', 'gateio', 'isolated', 0.0, 11.87413417771621), (False, 'futures', 'gateio', 'isolated', 0.0, 8.085708510208207), - (True, 'futures', 'binance', 'isolated', 0.05, 11.796534653465345), - (False, 'futures', 'binance', 'isolated', 0.05, 8.167171717171717), + (True, 'futures', 'binance', 'isolated', 0.05, 11.7874422442244), + (False, 'futures', 'binance', 'isolated', 0.05, 8.17644781144781), (True, 'futures', 'gateio', 'isolated', 0.05, 11.7804274688304), (False, 'futures', 'gateio', 'isolated', 0.05, 8.181423084697796), (True, 'futures', 'okx', 'isolated', 0.0, 11.87413417771621), @@ -845,6 +845,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, assert trade.open_order_id is None assert trade.open_rate == 10 assert trade.stake_amount == round(order['price'] * order['filled'] / leverage, 8) + assert pytest.approx(trade.liquidation_price) == liq_price # In case of rejected or expired order and partially filled order['status'] = 'expired' @@ -932,8 +933,6 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, assert trade.open_rate_requested == 10 # In case of custom entry price not float type - freqtrade.exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01)) - freqtrade.exchange.name = exchange_name order['status'] = 'open' order['id'] = '5568' freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price" @@ -946,7 +945,6 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, trade.is_short = is_short assert trade assert trade.open_rate_requested == 10 - assert trade.liquidation_price == liq_price # In case of too high stake amount From 220927289d2419046efcaf7bae1499effa537a54 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Apr 2022 19:10:04 +0200 Subject: [PATCH 203/254] Update documentation to highlight futures supported exchanges --- README.md | 8 ++++++++ docs/index.md | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/README.md b/README.md index 679dbcab0..cad39f9ac 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,14 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even - [X] [OKX](https://okx.com/) (Former OKEX) - [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ +### Experimentally, freqtrade also supports futures on the following exchanges + +- [X] [Binance](https://www.binance.com/) +- [X] [Gate.io](https://www.gate.io/ref/6266643) +- [X] [OKX](https://okx.com/). + +Please make sure to read the [exchange specific notes](docs/exchanges.md), as well as the [trading with leverage](docs/leverage.md) documentation before diving in. + ### Community tested Exchanges confirmed working by the community: diff --git a/docs/index.md b/docs/index.md index 2aa80c240..e0a88a381 100644 --- a/docs/index.md +++ b/docs/index.md @@ -51,6 +51,14 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual, - [X] [OKX](https://okx.com/) (Former OKEX) - [ ] [potentially many others through ccxt](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ +### Experimentally, freqtrade also supports futures on the following exchanges: + +- [X] [Binance](https://www.binance.com/) +- [X] [Gate.io](https://www.gate.io/ref/6266643) +- [X] [OKX](https://okx.com/). + +Please make sure to read the [exchange specific notes](exchanges.md), as well as the [trading with leverage](leverage.md) documentation before diving in. + ### Community tested Exchanges confirmed working by the community: From 46855221aab34d0c0687474c9e8a1af08cfa9916 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Apr 2022 19:58:19 +0200 Subject: [PATCH 204/254] Fix rounding issue with contract-sized pairs for dry-run orders --- freqtrade/exchange/exchange.py | 4 +++- freqtrade/freqtradebot.py | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index d4741bd64..b12751fff 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -785,7 +785,9 @@ class Exchange: rate: float, leverage: float, params: Dict = {}, stop_loss: bool = False) -> Dict[str, Any]: order_id = f'dry_run_{side}_{datetime.now().timestamp()}' - _amount = self.amount_to_precision(pair, amount) + # Rounding here must respect to contract sizes + _amount = self._contracts_to_amount( + pair, self.amount_to_precision(pair, self._amount_to_contracts(pair, amount))) dry_order: Dict[str, Any] = { 'id': order_id, 'symbol': pair, diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index c82f2c8fe..7c20a7f60 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -585,7 +585,6 @@ class FreqtradeBot(LoggingMixin): Executes a limit buy for the given pair :param pair: pair for which we want to create a LIMIT_BUY :param stake_amount: amount of stake-currency for the pair - :param leverage: amount of leverage applied to this trade :return: True if a buy order is created, false if it fails. """ time_in_force = self.strategy.order_time_in_force['entry'] From ca49821df011c8ebb7ec8586c08daf675d210bd4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 Apr 2022 06:29:14 +0200 Subject: [PATCH 205/254] Fix race condition for loop --- freqtrade/exchange/exchange.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index d4741bd64..8ecccbce0 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -9,6 +9,7 @@ import logging from copy import deepcopy from datetime import datetime, timedelta, timezone from math import ceil +from threading import Lock from typing import Any, Coroutine, Dict, List, Literal, Optional, Tuple, Union import arrow @@ -96,6 +97,9 @@ class Exchange: self._markets: Dict = {} self._trading_fees: Dict[str, Any] = {} self._leverage_tiers: Dict[str, List[Dict]] = {} + # Lock event loop. This is necessary to avoid race-conditions when using force* commands + # Due to funding fee fetching. + self._loop_lock = Lock() self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self._config: Dict = {} @@ -1775,7 +1779,8 @@ class Exchange: async def gather_stuff(): return await asyncio.gather(*input_coro, return_exceptions=True) - results = self.loop.run_until_complete(gather_stuff()) + with self._loop_lock: + results = self.loop.run_until_complete(gather_stuff()) for res in results: if isinstance(res, Exception): @@ -2032,9 +2037,10 @@ class Exchange: if not self.exchange_has("fetchTrades"): raise OperationalException("This exchange does not support downloading Trades.") - return self.loop.run_until_complete( - self._async_get_trade_history(pair=pair, since=since, - until=until, from_id=from_id)) + with self._loop_lock: + return self.loop.run_until_complete( + self._async_get_trade_history(pair=pair, since=since, + until=until, from_id=from_id)) @retrier def _get_funding_fees_from_exchange(self, pair: str, since: Union[datetime, int]) -> float: From 1e835896415191469a369c30cf5848d96004d7e3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 Apr 2022 06:59:03 +0200 Subject: [PATCH 206/254] Fix hyperopt --- freqtrade/optimize/hyperopt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 3ae975ca7..1dafb483c 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -468,6 +468,7 @@ class Hyperopt: self.backtesting.exchange._api = None self.backtesting.exchange._api_async = None self.backtesting.exchange.loop = None # type: ignore + self.backtesting.exchange._loop_lock = None # type: ignore # self.backtesting.exchange = None # type: ignore self.backtesting.pairlists = None # type: ignore From 2ef1181e16b8d2389f51687d3be67b78b26e01f8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 Apr 2022 07:33:30 +0200 Subject: [PATCH 207/254] Simplify trade __repr__ --- freqtrade/persistence/models.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index a9c07f12c..2cacc06e2 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -429,12 +429,10 @@ class LocalTrade(): def __repr__(self): open_since = self.open_date.strftime(DATETIME_PRINT_FORMAT) if self.is_open else 'closed' - leverage = self.leverage or 1.0 - is_short = self.is_short or False return ( f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, ' - f'is_short={is_short}, leverage={leverage}, ' + f'is_short={self.is_short or False}, leverage={self.leverage or 1.0}, ' f'open_rate={self.open_rate:.8f}, open_since={open_since})' ) From 64072f76b9760679bebb24b66cd4dc52ba85d40f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 Apr 2022 14:40:47 +0200 Subject: [PATCH 208/254] Don't fail scheduled ci tasks due to notification --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5bafe9cb8..0a87e2c81 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -321,6 +321,8 @@ jobs: notify-complete: needs: [ build_linux, build_macos, build_windows, docs_check, mypy_version_check ] runs-on: ubuntu-20.04 + # Discord notification can't handle schedule events + if: (github.event_name != 'schedule') steps: - name: Check user permission From 4c95996069dc161fa3c6aaba8cdc6ad9629a6250 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 Apr 2022 14:50:50 +0200 Subject: [PATCH 209/254] Add Permissions for notify-complete job --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 932649f61..9e7ebfc6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -324,6 +324,8 @@ jobs: notify-complete: needs: [ build_linux, build_macos, build_windows, docs_check, mypy_version_check ] runs-on: ubuntu-20.04 + permissions: + repository-projects: read steps: - name: Check user permission From cb5c3316d1e3dd63658de539c94eb559d91e4e73 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 Apr 2022 19:43:52 +0200 Subject: [PATCH 210/254] Simplify log output --- freqtrade/exchange/exchange.py | 4 ++-- tests/exchange/test_exchange.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 6bb4eb446..82dcacb51 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -171,7 +171,7 @@ class Exchange: self._api_async = self._init_ccxt( exchange_config, ccxt_async, ccxt_kwargs=ccxt_async_config) - logger.info('Using Exchange "%s"', self.name) + logger.info(f'Using Exchange "{self.name}"') if validate: # Check if timeframe is available @@ -559,7 +559,7 @@ class Exchange: # Therefore we also show that. raise OperationalException( f"The ccxt library does not provide the list of timeframes " - f"for the exchange \"{self.name}\" and this exchange " + f"for the exchange {self.name} and this exchange " f"is therefore not supported. ccxt fetchOHLCV: {self.exchange_has('fetchOHLCV')}") if timeframe and (timeframe not in self.timeframes): diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index a6918b6d4..689ffa4ce 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -909,7 +909,7 @@ def test_validate_timeframes_emulated_ohlcv_1(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') with pytest.raises(OperationalException, match=r'The ccxt library does not provide the list of timeframes ' - r'for the exchange ".*" and this exchange ' + r'for the exchange .* and this exchange ' r'is therefore not supported. *'): Exchange(default_conf) @@ -930,7 +930,7 @@ def test_validate_timeframes_emulated_ohlcvi_2(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') with pytest.raises(OperationalException, match=r'The ccxt library does not provide the list of timeframes ' - r'for the exchange ".*" and this exchange ' + r'for the exchange .* and this exchange ' r'is therefore not supported. *'): Exchange(default_conf) From d1a61f9c615fb7e5d9717c126d9280bccf3e30ec Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 28 Apr 2022 20:05:19 +0200 Subject: [PATCH 211/254] Don't start futures backtest if leverage-tiers don't contain pair --- freqtrade/optimize/backtesting.py | 8 ++++++++ tests/optimize/test_backtesting.py | 33 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 210eab39b..0f816f295 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -269,10 +269,18 @@ class Backtesting: candle_type=CandleType.from_string(self.exchange._ft_has["mark_ohlcv_price"]) ) # Combine data to avoid combining the data per trade. + unavailable_pairs = [] for pair in self.pairlists.whitelist: + if pair not in self.exchange._leverage_tiers: + unavailable_pairs.append(pair) + continue self.futures_data[pair] = funding_rates_dict[pair].merge( mark_rates_dict[pair], on='date', how="inner", suffixes=["_fund", "_mark"]) + if unavailable_pairs: + raise OperationalException( + f"Pairs {', '.join(unavailable_pairs)} got no leverage tiers available. " + "It is therefore impossible to backtest with this pair at the moment.") else: self.futures_data = {} diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index d7ee4a042..a51e1b654 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -1341,6 +1341,39 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat assert 'STRATEGY SUMMARY' in captured.out +@pytest.mark.filterwarnings("ignore:deprecated") +def test_backtest_start_futures_noliq(default_conf_usdt, mocker, + caplog, testdatadir, capsys): + # Tests detail-data loading + default_conf_usdt.update({ + "trading_mode": "futures", + "margin_mode": "isolated", + "use_exit_signal": True, + "exit_profit_only": False, + "exit_profit_offset": 0.0, + "ignore_roi_if_entry_signal": False, + "strategy": CURRENT_TEST_STRATEGY, + }) + patch_exchange(mocker) + + mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist', + PropertyMock(return_value=['HULUMULU/USDT', 'XRP/USDT'])) + # mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) + + patched_configuration_load_config_file(mocker, default_conf_usdt) + + args = [ + 'backtesting', + '--config', 'config.json', + '--datadir', str(testdatadir), + '--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'), + '--timeframe', '1h', + ] + args = get_args(args) + with pytest.raises(OperationalException, match=r"Pairs .* got no leverage tiers available\."): + start_backtesting(args) + + @pytest.mark.filterwarnings("ignore:deprecated") def test_backtest_start_nomock_futures(default_conf_usdt, mocker, caplog, testdatadir, capsys): From 21df1b0db32f04bbb362c33afa483f17c328bfb7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 27 Apr 2022 13:13:11 +0200 Subject: [PATCH 212/254] Use ORJSON for http responses --- freqtrade/rpc/api_server/webserver.py | 4 ++-- requirements.txt | 2 ++ setup.py | 1 + tests/rpc/test_rpc_apiserver.py | 3 +-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py index 63812f52f..0da129583 100644 --- a/freqtrade/rpc/api_server/webserver.py +++ b/freqtrade/rpc/api_server/webserver.py @@ -2,7 +2,7 @@ import logging from ipaddress import IPv4Address from typing import Any, Dict -import rapidjson +import orjson import uvicorn from fastapi import Depends, FastAPI from fastapi.middleware.cors import CORSMiddleware @@ -24,7 +24,7 @@ class FTJSONResponse(JSONResponse): Use rapidjson for responses Handles NaN and Inf / -Inf in a javascript way by default. """ - return rapidjson.dumps(content).encode("utf-8") + return orjson.dumps(content, option=orjson.OPT_SERIALIZE_NUMPY) class ApiServer(RPCHandler): diff --git a/requirements.txt b/requirements.txt index de14b9f2c..ab8329979 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,6 +27,8 @@ py_find_1st==1.1.5 # Load ticker files 30% faster python-rapidjson==1.6 +# Properly format api responses +orjson==3.6.8 # Notify systemd sdnotify==0.3.2 diff --git a/setup.py b/setup.py index 250cafdc9..c5e418d0d 100644 --- a/setup.py +++ b/setup.py @@ -57,6 +57,7 @@ setup( 'pycoingecko', 'py_find_1st', 'python-rapidjson', + 'orjson', 'sdnotify', 'colorama', 'jinja2', diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 4910213b4..43f783a53 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -13,7 +13,6 @@ import uvicorn from fastapi import FastAPI from fastapi.exceptions import HTTPException from fastapi.testclient import TestClient -from numpy import isnan from requests.auth import _basic_auth_str from freqtrade.__init__ import __version__ @@ -985,7 +984,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, assert_response(rc) resp_values = rc.json() assert len(resp_values) == 4 - assert isnan(resp_values[0]['profit_abs']) + assert resp_values[0]['profit_abs'] is None def test_api_version(botclient): From 48ff788e827ecb16a59d09260e03133ea13e6085 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Apr 2022 09:53:05 +0000 Subject: [PATCH 213/254] Clarify that stoploss is required closes #6740 --- docs/strategy-callbacks.md | 36 ++++++++++++++++++------------------ freqtrade/constants.py | 2 ++ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 563b5a2cb..c5639d581 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -122,11 +122,11 @@ See [Dataframe access](strategy-advanced.md#dataframe-access) for more informati ## Custom stoploss -Called for open trade every throttling iteration (roughly every 5 seconds) until a trade is closed. +Called for open trade every iteration (roughly every 5 seconds) until a trade is closed. The usage of the custom stoploss method must be enabled by setting `use_custom_stoploss=True` on the strategy object. -The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss (before this method is called for the first time for a trade). +The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss (before this method is called for the first time for a trade), and is still mandatory. The method must return a stoploss value (float / number) as a percentage of the current price. E.g. If the `current_rate` is 200 USD, then returning `0.02` will set the stoploss price 2% lower, at 196 USD. @@ -365,13 +365,13 @@ class AwesomeStrategy(IStrategy): # ... populate_* methods - def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float, + def custom_entry_price(self, pair: str, current_time: datetime, proposed_rate: float, entry_tag: Optional[str], side: str, **kwargs) -> float: dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) new_entryprice = dataframe['bollinger_10_lowerband'].iat[-1] - + return new_entryprice def custom_exit_price(self, pair: str, trade: Trade, @@ -381,14 +381,14 @@ class AwesomeStrategy(IStrategy): dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe) new_exitprice = dataframe['bollinger_10_upperband'].iat[-1] - + return new_exitprice ``` !!! Warning - Modifying entry and exit prices will only work for limit orders. Depending on the price chosen, this can result in a lot of unfilled orders. By default the maximum allowed distance between the current price and the custom price is 2%, this value can be changed in config with the `custom_price_max_distance_ratio` parameter. - **Example**: + Modifying entry and exit prices will only work for limit orders. Depending on the price chosen, this can result in a lot of unfilled orders. By default the maximum allowed distance between the current price and the custom price is 2%, this value can be changed in config with the `custom_price_max_distance_ratio` parameter. + **Example**: If the new_entryprice is 97, the proposed_rate is 100 and the `custom_price_max_distance_ratio` is set to 2%, The retained valid custom entry price will be 98, which is 2% below the current (proposed) rate. !!! Warning "Backtesting" @@ -430,7 +430,7 @@ class AwesomeStrategy(IStrategy): 'exit': 60 * 25 } - def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', + def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order', current_time: datetime, **kwargs) -> bool: if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5): return True @@ -508,7 +508,7 @@ class AwesomeStrategy(IStrategy): # ... populate_* methods def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, - time_in_force: str, current_time: datetime, entry_tag: Optional[str], + time_in_force: str, current_time: datetime, entry_tag: Optional[str], side: str, **kwargs) -> bool: """ Called right before placing a entry order. @@ -616,35 +616,35 @@ from freqtrade.persistence import Trade class DigDeeperStrategy(IStrategy): - + position_adjustment_enable = True - + # Attempts to handle large drops with DCA. High stoploss is required. stoploss = -0.30 - + # ... populate_* methods - + # Example specific variables max_entry_position_adjustment = 3 # This number is explained a bit further down max_dca_multiplier = 5.5 - + # This is called when placing the initial order (opening trade) def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, proposed_stake: float, min_stake: float, max_stake: float, entry_tag: Optional[str], side: str, **kwargs) -> float: - + # We need to leave most of the funds for possible further DCA orders # This also applies to fixed stakes return proposed_stake / self.max_dca_multiplier - + def adjust_trade_position(self, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, min_stake: float, max_stake: float, **kwargs): """ Custom trade adjustment logic, returning the stake amount that a trade should be increased. This means extra buy orders with additional fees. - + :param trade: trade object. :param current_time: datetime object, containing the current datetime :param current_rate: Current buy rate. @@ -654,7 +654,7 @@ class DigDeeperStrategy(IStrategy): :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. :return float: Stake amount to adjust your trade """ - + if current_profit > -0.05: return None diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 1a21ec77f..3b98ce56b 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -459,6 +459,8 @@ SCHEMA_BACKTEST_REQUIRED = [ 'stake_currency', 'stake_amount', 'dry_run_wallet', + 'stoploss', + 'minimal_roi', 'dataformat_ohlcv', 'dataformat_trades', ] From b6bee45e82604a06f3143aa10967c0641847e779 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Apr 2022 09:54:54 +0000 Subject: [PATCH 214/254] Exclude user_data from isort --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 50f0242a8..e8d5ed47e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ exclude = ''' line_length = 100 multi_line_output=0 lines_after_imports=2 -skip_glob = ["**/.env*", "**/env/*", "**/.venv/*", "**/docs/*"] +skip_glob = ["**/.env*", "**/env/*", "**/.venv/*", "**/docs/*", "**/user_data/*"] [tool.pytest.ini_options] asyncio_mode = "auto" From da7a6f58f94db81d3f436dacadbdf2c7a903ee1d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Apr 2022 17:46:33 +0200 Subject: [PATCH 215/254] Revert requiring stoploss for backtest/hyperopt --- freqtrade/constants.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 3b98ce56b..1a21ec77f 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -459,8 +459,6 @@ SCHEMA_BACKTEST_REQUIRED = [ 'stake_currency', 'stake_amount', 'dry_run_wallet', - 'stoploss', - 'minimal_roi', 'dataformat_ohlcv', 'dataformat_trades', ] From f96c552c46a56ad5822d764591f7828a5a2dc4bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=AE=AE=E0=AE=A9=E0=AF=8B=E0=AE=9C=E0=AF=8D=E0=AE=95?= =?UTF-8?q?=E0=AF=81=E0=AE=AE=E0=AE=BE=E0=AE=B0=E0=AF=8D=20=E0=AE=AA?= =?UTF-8?q?=E0=AE=B4=E0=AE=A9=E0=AE=BF=E0=AE=9A=E0=AF=8D=E0=AE=9A=E0=AE=BE?= =?UTF-8?q?=E0=AE=AE=E0=AE=BF?= Date: Fri, 29 Apr 2022 22:14:02 +0530 Subject: [PATCH 216/254] Update PULL_REQUEST_TEMPLATE.md Added instructions as comments --- .github/PULL_REQUEST_TEMPLATE.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7c0655b20..25a9761e2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,17 +1,17 @@ -Thank you for sending your pull request. But first, have you included + ## Summary -Explain in one sentence the goal of this PR + Solve the issue: #___ ## Quick changelog -- -- +- +- ## What's new? -*Explain in details what this PR solve or improve. You can include visuals.* + From fbd142844fff0065bdac6a9e8770990faf289ab3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Apr 2022 19:37:13 +0200 Subject: [PATCH 217/254] Refactor bt-caching stuff to it's own module --- freqtrade/data/btanalysis.py | 3 +- freqtrade/misc.py | 35 +----------------------- freqtrade/optimize/backtest_caching.py | 38 ++++++++++++++++++++++++++ freqtrade/optimize/backtesting.py | 2 +- freqtrade/optimize/optimize_reports.py | 4 +-- tests/optimize/test_backtesting.py | 2 +- 6 files changed, 45 insertions(+), 39 deletions(-) create mode 100644 freqtrade/optimize/backtest_caching.py diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 206a6f5f3..0c8e721c0 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -12,7 +12,8 @@ import pandas as pd from freqtrade.constants import LAST_BT_RESULT_FN from freqtrade.exceptions import OperationalException -from freqtrade.misc import get_backtest_metadata_filename, json_load +from freqtrade.misc import json_load +from freqtrade.optimize.backtest_caching import get_backtest_metadata_filename from freqtrade.persistence import LocalTrade, Trade, init_db diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 55a533725..c3968e61c 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -2,13 +2,11 @@ Various tool function for Freqtrade and scripts """ import gzip -import hashlib import logging import re -from copy import deepcopy from datetime import datetime from pathlib import Path -from typing import Any, Iterator, List, Union +from typing import Any, Iterator, List from typing.io import IO from urllib.parse import urlparse @@ -251,34 +249,3 @@ def parse_db_uri_for_logging(uri: str): return uri pwd = parsed_db_uri.netloc.split(':')[1].split('@')[0] return parsed_db_uri.geturl().replace(f':{pwd}@', ':*****@') - - -def get_strategy_run_id(strategy) -> str: - """ - Generate unique identification hash for a backtest run. Identical config and strategy file will - always return an identical hash. - :param strategy: strategy object. - :return: hex string id. - """ - digest = hashlib.sha1() - config = deepcopy(strategy.config) - - # Options that have no impact on results of individual backtest. - not_important_keys = ('strategy_list', 'original_config', 'telegram', 'api_server') - for k in not_important_keys: - if k in config: - del config[k] - - # Explicitly allow NaN values (e.g. max_open_trades). - # as it does not matter for getting the hash. - digest.update(rapidjson.dumps(config, default=str, - number_mode=rapidjson.NM_NAN).encode('utf-8')) - with open(strategy.__file__, 'rb') as fp: - digest.update(fp.read()) - return digest.hexdigest().lower() - - -def get_backtest_metadata_filename(filename: Union[Path, str]) -> Path: - """Return metadata filename for specified backtest results file.""" - filename = Path(filename) - return filename.parent / Path(f'{filename.stem}.meta{filename.suffix}') diff --git a/freqtrade/optimize/backtest_caching.py b/freqtrade/optimize/backtest_caching.py new file mode 100644 index 000000000..c2a9903fa --- /dev/null +++ b/freqtrade/optimize/backtest_caching.py @@ -0,0 +1,38 @@ +import hashlib +from copy import deepcopy +from pathlib import Path +from typing import Union + +import rapidjson + + +def get_strategy_run_id(strategy) -> str: + """ + Generate unique identification hash for a backtest run. Identical config and strategy file will + always return an identical hash. + :param strategy: strategy object. + :return: hex string id. + """ + digest = hashlib.sha1() + config = deepcopy(strategy.config) + + # Options that have no impact on results of individual backtest. + not_important_keys = ('strategy_list', 'original_config', 'telegram', 'api_server') + for k in not_important_keys: + if k in config: + del config[k] + + # Explicitly allow NaN values (e.g. max_open_trades). + # as it does not matter for getting the hash. + digest.update(rapidjson.dumps(config, default=str, + number_mode=rapidjson.NM_NAN).encode('utf-8')) + + with open(strategy.__file__, 'rb') as fp: + digest.update(fp.read()) + return digest.hexdigest().lower() + + +def get_backtest_metadata_filename(filename: Union[Path, str]) -> Path: + """Return metadata filename for specified backtest results file.""" + filename = Path(filename) + return filename.parent / Path(f'{filename.stem}.meta{filename.suffix}') diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 0f816f295..db4496f0f 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -24,8 +24,8 @@ from freqtrade.enums import (BacktestState, CandleType, ExitCheckTuple, ExitType TradingMode) from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds -from freqtrade.misc import get_strategy_run_id from freqtrade.mixins import LoggingMixin +from freqtrade.optimize.backtest_caching import get_strategy_run_id from freqtrade.optimize.bt_progress import BTProgress from freqtrade.optimize.optimize_reports import (generate_backtest_stats, show_backtest_results, store_backtest_signal_candles, diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index dd058aff4..1d58dc339 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -11,8 +11,8 @@ from tabulate import tabulate from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN, UNLIMITED_STAKE_AMOUNT from freqtrade.data.btanalysis import (calculate_cagr, calculate_csum, calculate_market_change, calculate_max_drawdown) -from freqtrade.misc import (decimals_per_coin, file_dump_joblib, file_dump_json, - get_backtest_metadata_filename, round_coin_value) +from freqtrade.misc import decimals_per_coin, file_dump_joblib, file_dump_json, round_coin_value +from freqtrade.optimize.backtest_caching import get_backtest_metadata_filename logger = logging.getLogger(__name__) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index a51e1b654..7494155b4 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -22,7 +22,7 @@ from freqtrade.data.history import get_timerange from freqtrade.enums import ExitType, RunMode from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exchange.exchange import timeframe_to_next_date -from freqtrade.misc import get_strategy_run_id +from freqtrade.optimize.backtest_caching import get_strategy_run_id from freqtrade.optimize.backtesting import Backtesting from freqtrade.persistence import LocalTrade from freqtrade.resolvers import StrategyResolver From 43049e04652483ff039ee7765a0181d2ed1ca337 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 29 Apr 2022 19:44:17 +0200 Subject: [PATCH 218/254] Evict cache if parameter file changed closes #6735 --- freqtrade/optimize/backtest_caching.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtest_caching.py b/freqtrade/optimize/backtest_caching.py index c2a9903fa..d9d270072 100644 --- a/freqtrade/optimize/backtest_caching.py +++ b/freqtrade/optimize/backtest_caching.py @@ -26,7 +26,9 @@ def get_strategy_run_id(strategy) -> str: # as it does not matter for getting the hash. digest.update(rapidjson.dumps(config, default=str, number_mode=rapidjson.NM_NAN).encode('utf-8')) - + # Include _ft_params_from_file - so changing parameter files cause cache eviction + digest.update(rapidjson.dumps( + strategy._ft_params_from_file, default=str, number_mode=rapidjson.NM_NAN).encode('utf-8')) with open(strategy.__file__, 'rb') as fp: digest.update(fp.read()) return digest.hexdigest().lower() From f23faac36880027896de8d7bcc21f5af9edd6714 Mon Sep 17 00:00:00 2001 From: erdieee <58039191+erdieee@users.noreply.github.com> Date: Fri, 29 Apr 2022 20:10:50 +0200 Subject: [PATCH 219/254] Fix config_examples typo --- config_examples/config_binance.example.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_examples/config_binance.example.json b/config_examples/config_binance.example.json index ad8862afa..35b9fcd20 100644 --- a/config_examples/config_binance.example.json +++ b/config_examples/config_binance.example.json @@ -90,7 +90,7 @@ }, "bot_name": "freqtrade", "initial_state": "running", - "force_enter_enable": false, + "force_entry_enable": false, "internals": { "process_throttle_secs": 5 } From 788d9f5b55cef5657ad476d6feef8cbd227a3dec Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 29 Apr 2022 22:20:26 -0600 Subject: [PATCH 220/254] updated bot_start documentation with working example --- docs/strategy-callbacks.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index e0cfbe8c1..b01959e10 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -39,7 +39,10 @@ class AwesomeStrategy(IStrategy): Called only once after bot instantiation. :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. """ - self.dp['remote_data'] = requests.get('https://some_remote_source.example.com') + if self.config['runmode'].value in ('live', 'dry_run'): + # Assign this to the class by using self.* + # can then be used by populate_* methods + self.remote_data = requests.get('https://some_remote_source.example.com') ``` ## Bot loop start From 23431a71064367496d8250ca7a7dd20b97da63d4 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 29 Apr 2022 22:21:22 -0600 Subject: [PATCH 221/254] removed invalid plotting and test_default_strategy tests for bot_start, edited edge test --- tests/optimize/test_edge_cli.py | 2 +- tests/strategy/test_default_strategy.py | 1 - tests/test_plotting.py | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index d9711b318..8241a5362 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -94,7 +94,7 @@ def test_edge_init(mocker, edge_conf) -> None: assert edge_cli.config == edge_conf assert edge_cli.config['stake_amount'] == 'unlimited' assert callable(edge_cli.edge.calculate) - assert edge_conf['strategy'].bot_started is True + assert edge_cli.strategy.bot_started is True def test_edge_init_fee(mocker, edge_conf) -> None: diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py index a60274afd..5cb8fce16 100644 --- a/tests/strategy/test_default_strategy.py +++ b/tests/strategy/test_default_strategy.py @@ -32,7 +32,6 @@ def test_strategy_test_v3(result, fee, is_short, side): assert type(indicators) is DataFrame assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame assert type(strategy.populate_sell_trend(indicators, metadata)) is DataFrame - assert strategy.bot_started is True trade = Trade( open_rate=19_000, diff --git a/tests/test_plotting.py b/tests/test_plotting.py index f0a28c4eb..97f367608 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -58,7 +58,6 @@ def test_init_plotscript(default_conf, mocker, testdatadir): assert "ohlcv" in ret assert "TRX/BTC" in ret["ohlcv"] assert "ADA/BTC" in ret["ohlcv"] - assert default_conf['strategy'].bot_started is True def test_add_indicators(default_conf, testdatadir, caplog): From 8756e7d9a1febecfae12649cc8ed21952d2f0086 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Fri, 29 Apr 2022 23:35:08 -0600 Subject: [PATCH 222/254] flake8 linting --- freqtrade/edge/edge_positioning.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 5dc84f172..fb9041574 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -90,7 +90,7 @@ class Edge: self.config['exchange']['pair_whitelist'], list(self.exchange.markets))[0]) except IndexError: self.fee = None - + strategy.bot_start() def calculate(self, pairs: List[str]) -> bool: From 09b74cebce95c80bec0f6982bf9b865523e49874 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Apr 2022 08:55:07 +0200 Subject: [PATCH 223/254] Move edge bot_loop_start to edge_cli (otherwise it's called twice when running trade mode with edge on). --- docs/strategy-callbacks.md | 6 +++--- freqtrade/edge/edge_positioning.py | 2 -- freqtrade/optimize/edge_cli.py | 1 + 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 27777c2ce..5ff499b01 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -24,11 +24,11 @@ Currently available callbacks: ## Bot start -A simple callback which is called once when the bot starts. +A simple callback which is called once when the strategy is loaded. This can be used to perform actions that must only be performed once and runs after dataprovider and wallet are set ``` python -import asyncio +import requests class AwesomeStrategy(IStrategy): @@ -42,7 +42,7 @@ class AwesomeStrategy(IStrategy): if self.config['runmode'].value in ('live', 'dry_run'): # Assign this to the class by using self.* # can then be used by populate_* methods - self.remote_data = requests.get('https://some_remote_source.example.com') + self.cust_remote_data = requests.get('https://some_remote_source.example.com') ``` ## Bot loop start diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index fb9041574..2fe41a17b 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -91,8 +91,6 @@ class Edge: except IndexError: self.fee = None - strategy.bot_start() - def calculate(self, pairs: List[str]) -> bool: if self.fee is None and pairs: self.fee = self.exchange.get_fee(pairs[0]) diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py index cc9bafb0b..30eabecd0 100644 --- a/freqtrade/optimize/edge_cli.py +++ b/freqtrade/optimize/edge_cli.py @@ -44,6 +44,7 @@ class EdgeCli: self.edge._timerange = TimeRange.parse_timerange(None if self.config.get( 'timerange') is None else str(self.config.get('timerange'))) + self.strategy.bot_start() def start(self) -> None: result = self.edge.calculate(self.config['exchange']['pair_whitelist']) From 2acb68e6e24fdb6920b89fa2992d55d7f7c91bcf Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Apr 2022 13:59:23 +0200 Subject: [PATCH 224/254] Move hyperopt-loss functions to their own package --- freqtrade/optimize/{ => hyperopt_loss}/hyperopt_loss_calmar.py | 0 .../optimize/{ => hyperopt_loss}/hyperopt_loss_max_drawdown.py | 0 .../optimize/{ => hyperopt_loss}/hyperopt_loss_onlyprofit.py | 0 .../{ => hyperopt_loss}/hyperopt_loss_profit_drawdown.py | 0 freqtrade/optimize/{ => hyperopt_loss}/hyperopt_loss_sharpe.py | 0 .../optimize/{ => hyperopt_loss}/hyperopt_loss_sharpe_daily.py | 0 .../{ => hyperopt_loss}/hyperopt_loss_short_trade_dur.py | 0 freqtrade/optimize/{ => hyperopt_loss}/hyperopt_loss_sortino.py | 0 .../optimize/{ => hyperopt_loss}/hyperopt_loss_sortino_daily.py | 0 freqtrade/resolvers/hyperopt_resolver.py | 2 +- tests/optimize/test_hyperoptloss.py | 2 +- 11 files changed, 2 insertions(+), 2 deletions(-) rename freqtrade/optimize/{ => hyperopt_loss}/hyperopt_loss_calmar.py (100%) rename freqtrade/optimize/{ => hyperopt_loss}/hyperopt_loss_max_drawdown.py (100%) rename freqtrade/optimize/{ => hyperopt_loss}/hyperopt_loss_onlyprofit.py (100%) rename freqtrade/optimize/{ => hyperopt_loss}/hyperopt_loss_profit_drawdown.py (100%) rename freqtrade/optimize/{ => hyperopt_loss}/hyperopt_loss_sharpe.py (100%) rename freqtrade/optimize/{ => hyperopt_loss}/hyperopt_loss_sharpe_daily.py (100%) rename freqtrade/optimize/{ => hyperopt_loss}/hyperopt_loss_short_trade_dur.py (100%) rename freqtrade/optimize/{ => hyperopt_loss}/hyperopt_loss_sortino.py (100%) rename freqtrade/optimize/{ => hyperopt_loss}/hyperopt_loss_sortino_daily.py (100%) diff --git a/freqtrade/optimize/hyperopt_loss_calmar.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py similarity index 100% rename from freqtrade/optimize/hyperopt_loss_calmar.py rename to freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py diff --git a/freqtrade/optimize/hyperopt_loss_max_drawdown.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown.py similarity index 100% rename from freqtrade/optimize/hyperopt_loss_max_drawdown.py rename to freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown.py diff --git a/freqtrade/optimize/hyperopt_loss_onlyprofit.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_onlyprofit.py similarity index 100% rename from freqtrade/optimize/hyperopt_loss_onlyprofit.py rename to freqtrade/optimize/hyperopt_loss/hyperopt_loss_onlyprofit.py diff --git a/freqtrade/optimize/hyperopt_loss_profit_drawdown.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py similarity index 100% rename from freqtrade/optimize/hyperopt_loss_profit_drawdown.py rename to freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py diff --git a/freqtrade/optimize/hyperopt_loss_sharpe.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe.py similarity index 100% rename from freqtrade/optimize/hyperopt_loss_sharpe.py rename to freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe.py diff --git a/freqtrade/optimize/hyperopt_loss_sharpe_daily.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe_daily.py similarity index 100% rename from freqtrade/optimize/hyperopt_loss_sharpe_daily.py rename to freqtrade/optimize/hyperopt_loss/hyperopt_loss_sharpe_daily.py diff --git a/freqtrade/optimize/hyperopt_loss_short_trade_dur.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_short_trade_dur.py similarity index 100% rename from freqtrade/optimize/hyperopt_loss_short_trade_dur.py rename to freqtrade/optimize/hyperopt_loss/hyperopt_loss_short_trade_dur.py diff --git a/freqtrade/optimize/hyperopt_loss_sortino.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino.py similarity index 100% rename from freqtrade/optimize/hyperopt_loss_sortino.py rename to freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino.py diff --git a/freqtrade/optimize/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino_daily.py similarity index 100% rename from freqtrade/optimize/hyperopt_loss_sortino_daily.py rename to freqtrade/optimize/hyperopt_loss/hyperopt_loss_sortino_daily.py diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py index e3c234f60..bcfe5e1d8 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -23,7 +23,7 @@ class HyperOptLossResolver(IResolver): object_type = IHyperOptLoss object_type_str = "HyperoptLoss" user_subdir = USERPATH_HYPEROPTS - initial_search_path = Path(__file__).parent.parent.joinpath('optimize').resolve() + initial_search_path = Path(__file__).parent.parent.joinpath('optimize/hyperopt_loss').resolve() @staticmethod def load_hyperoptloss(config: Dict) -> IHyperOptLoss: diff --git a/tests/optimize/test_hyperoptloss.py b/tests/optimize/test_hyperoptloss.py index e3f6daf6c..4ec80ef49 100644 --- a/tests/optimize/test_hyperoptloss.py +++ b/tests/optimize/test_hyperoptloss.py @@ -4,7 +4,7 @@ from unittest.mock import MagicMock import pytest from freqtrade.exceptions import OperationalException -from freqtrade.optimize.hyperopt_loss_short_trade_dur import ShortTradeDurHyperOptLoss +from freqtrade.optimize.hyperopt_loss.hyperopt_loss_short_trade_dur import ShortTradeDurHyperOptLoss from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver From c6c569b77288519c5a7d2016d6a291ee8d68b9ea Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Apr 2022 14:47:27 +0200 Subject: [PATCH 225/254] chore: split BTAnalyais to metrics --- freqtrade/data/btanalysis.py | 167 +---------------- freqtrade/data/metrics.py | 173 ++++++++++++++++++ .../hyperopt_loss/hyperopt_loss_calmar.py | 2 +- .../hyperopt_loss_max_drawdown.py | 2 +- .../hyperopt_loss_profit_drawdown.py | 2 +- freqtrade/optimize/optimize_reports.py | 4 +- freqtrade/plot/plotting.py | 7 +- .../protections/max_drawdown_protection.py | 2 +- tests/data/test_btanalysis.py | 8 +- tests/test_plotting.py | 3 +- 10 files changed, 190 insertions(+), 180 deletions(-) create mode 100644 freqtrade/data/metrics.py diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 0c8e721c0..e29d9ebe4 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -5,7 +5,7 @@ import logging from copy import copy from datetime import datetime, timezone from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Any, Dict, List, Optional, Union import numpy as np import pandas as pd @@ -400,168 +400,3 @@ def extract_trades_of_period(dataframe: pd.DataFrame, trades: pd.DataFrame, trades = trades.loc[(trades['open_date'] >= trades_start) & (trades['close_date'] <= trades_stop)] return trades - - -def calculate_market_change(data: Dict[str, pd.DataFrame], column: str = "close") -> float: - """ - Calculate market change based on "column". - Calculation is done by taking the first non-null and the last non-null element of each column - and calculating the pctchange as "(last - first) / first". - Then the results per pair are combined as mean. - - :param data: Dict of Dataframes, dict key should be pair. - :param column: Column in the original dataframes to use - :return: - """ - tmp_means = [] - for pair, df in data.items(): - start = df[column].dropna().iloc[0] - end = df[column].dropna().iloc[-1] - tmp_means.append((end - start) / start) - - return float(np.mean(tmp_means)) - - -def combine_dataframes_with_mean(data: Dict[str, pd.DataFrame], - column: str = "close") -> pd.DataFrame: - """ - Combine multiple dataframes "column" - :param data: Dict of Dataframes, dict key should be pair. - :param column: Column in the original dataframes to use - :return: DataFrame with the column renamed to the dict key, and a column - named mean, containing the mean of all pairs. - :raise: ValueError if no data is provided. - """ - df_comb = pd.concat([data[pair].set_index('date').rename( - {column: pair}, axis=1)[pair] for pair in data], axis=1) - - df_comb['mean'] = df_comb.mean(axis=1) - - return df_comb - - -def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str, - timeframe: str) -> pd.DataFrame: - """ - Adds a column `col_name` with the cumulative profit for the given trades array. - :param df: DataFrame with date index - :param trades: DataFrame containing trades (requires columns close_date and profit_abs) - :param col_name: Column name that will be assigned the results - :param timeframe: Timeframe used during the operations - :return: Returns df with one additional column, col_name, containing the cumulative profit. - :raise: ValueError if trade-dataframe was found empty. - """ - if len(trades) == 0: - raise ValueError("Trade dataframe empty.") - from freqtrade.exchange import timeframe_to_minutes - timeframe_minutes = timeframe_to_minutes(timeframe) - # Resample to timeframe to make sure trades match candles - _trades_sum = trades.resample(f'{timeframe_minutes}min', on='close_date' - )[['profit_abs']].sum() - df.loc[:, col_name] = _trades_sum['profit_abs'].cumsum() - # Set first value to 0 - df.loc[df.iloc[0].name, col_name] = 0 - # FFill to get continuous - df[col_name] = df[col_name].ffill() - return df - - -def _calc_drawdown_series(profit_results: pd.DataFrame, *, date_col: str, value_col: str - ) -> pd.DataFrame: - max_drawdown_df = pd.DataFrame() - max_drawdown_df['cumulative'] = profit_results[value_col].cumsum() - max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax() - max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value'] - max_drawdown_df['date'] = profit_results.loc[:, date_col] - return max_drawdown_df - - -def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date', - value_col: str = 'profit_ratio' - ): - """ - Calculate max drawdown and the corresponding close dates - :param trades: DataFrame containing trades (requires columns close_date and profit_ratio) - :param date_col: Column in DataFrame to use for dates (defaults to 'close_date') - :param value_col: Column in DataFrame to use for values (defaults to 'profit_ratio') - :return: Tuple (float, highdate, lowdate, highvalue, lowvalue) with absolute max drawdown, - high and low time and high and low value. - :raise: ValueError if trade-dataframe was found empty. - """ - if len(trades) == 0: - raise ValueError("Trade dataframe empty.") - profit_results = trades.sort_values(date_col).reset_index(drop=True) - max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col) - - return max_drawdown_df - - -def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date', - value_col: str = 'profit_abs', starting_balance: float = 0 - ) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float, float]: - """ - Calculate max drawdown and the corresponding close dates - :param trades: DataFrame containing trades (requires columns close_date and profit_ratio) - :param date_col: Column in DataFrame to use for dates (defaults to 'close_date') - :param value_col: Column in DataFrame to use for values (defaults to 'profit_abs') - :param starting_balance: Portfolio starting balance - properly calculate relative drawdown. - :return: Tuple (float, highdate, lowdate, highvalue, lowvalue, relative_drawdown) - with absolute max drawdown, high and low time and high and low value, - and the relative account drawdown - :raise: ValueError if trade-dataframe was found empty. - """ - if len(trades) == 0: - raise ValueError("Trade dataframe empty.") - profit_results = trades.sort_values(date_col).reset_index(drop=True) - max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col) - - idxmin = max_drawdown_df['drawdown'].idxmin() - if idxmin == 0: - raise ValueError("No losing trade, therefore no drawdown.") - high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(), date_col] - low_date = profit_results.loc[idxmin, date_col] - high_val = max_drawdown_df.loc[max_drawdown_df.iloc[:idxmin] - ['high_value'].idxmax(), 'cumulative'] - low_val = max_drawdown_df.loc[idxmin, 'cumulative'] - max_drawdown_rel = 0.0 - if high_val + starting_balance != 0: - max_drawdown_rel = (high_val - low_val) / (high_val + starting_balance) - - return ( - abs(min(max_drawdown_df['drawdown'])), - high_date, - low_date, - high_val, - low_val, - max_drawdown_rel - ) - - -def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> Tuple[float, float]: - """ - Calculate min/max cumsum of trades, to show if the wallet/stake amount ratio is sane - :param trades: DataFrame containing trades (requires columns close_date and profit_percent) - :param starting_balance: Add starting balance to results, to show the wallets high / low points - :return: Tuple (float, float) with cumsum of profit_abs - :raise: ValueError if trade-dataframe was found empty. - """ - if len(trades) == 0: - raise ValueError("Trade dataframe empty.") - - csum_df = pd.DataFrame() - csum_df['sum'] = trades['profit_abs'].cumsum() - csum_min = csum_df['sum'].min() + starting_balance - csum_max = csum_df['sum'].max() + starting_balance - - return csum_min, csum_max - - -def calculate_cagr(days_passed: int, starting_balance: float, final_balance: float) -> float: - """ - Calculate CAGR - :param days_passed: Days passed between start and ending balance - :param starting_balance: Starting balance - :param final_balance: Final balance to calculate CAGR against - :return: CAGR - """ - return (final_balance / starting_balance) ** (1 / (days_passed / 365)) - 1 diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py new file mode 100644 index 000000000..44d5ce6ec --- /dev/null +++ b/freqtrade/data/metrics.py @@ -0,0 +1,173 @@ +import logging +from typing import Dict, Tuple + +import numpy as np +import pandas as pd + + +logger = logging.getLogger(__name__) + + +def calculate_market_change(data: Dict[str, pd.DataFrame], column: str = "close") -> float: + """ + Calculate market change based on "column". + Calculation is done by taking the first non-null and the last non-null element of each column + and calculating the pctchange as "(last - first) / first". + Then the results per pair are combined as mean. + + :param data: Dict of Dataframes, dict key should be pair. + :param column: Column in the original dataframes to use + :return: + """ + tmp_means = [] + for pair, df in data.items(): + start = df[column].dropna().iloc[0] + end = df[column].dropna().iloc[-1] + tmp_means.append((end - start) / start) + + return float(np.mean(tmp_means)) + + +def combine_dataframes_with_mean(data: Dict[str, pd.DataFrame], + column: str = "close") -> pd.DataFrame: + """ + Combine multiple dataframes "column" + :param data: Dict of Dataframes, dict key should be pair. + :param column: Column in the original dataframes to use + :return: DataFrame with the column renamed to the dict key, and a column + named mean, containing the mean of all pairs. + :raise: ValueError if no data is provided. + """ + df_comb = pd.concat([data[pair].set_index('date').rename( + {column: pair}, axis=1)[pair] for pair in data], axis=1) + + df_comb['mean'] = df_comb.mean(axis=1) + + return df_comb + + +def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str, + timeframe: str) -> pd.DataFrame: + """ + Adds a column `col_name` with the cumulative profit for the given trades array. + :param df: DataFrame with date index + :param trades: DataFrame containing trades (requires columns close_date and profit_abs) + :param col_name: Column name that will be assigned the results + :param timeframe: Timeframe used during the operations + :return: Returns df with one additional column, col_name, containing the cumulative profit. + :raise: ValueError if trade-dataframe was found empty. + """ + if len(trades) == 0: + raise ValueError("Trade dataframe empty.") + from freqtrade.exchange import timeframe_to_minutes + timeframe_minutes = timeframe_to_minutes(timeframe) + # Resample to timeframe to make sure trades match candles + _trades_sum = trades.resample(f'{timeframe_minutes}min', on='close_date' + )[['profit_abs']].sum() + df.loc[:, col_name] = _trades_sum['profit_abs'].cumsum() + # Set first value to 0 + df.loc[df.iloc[0].name, col_name] = 0 + # FFill to get continuous + df[col_name] = df[col_name].ffill() + return df + + +def _calc_drawdown_series(profit_results: pd.DataFrame, *, date_col: str, value_col: str + ) -> pd.DataFrame: + max_drawdown_df = pd.DataFrame() + max_drawdown_df['cumulative'] = profit_results[value_col].cumsum() + max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax() + max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value'] + max_drawdown_df['date'] = profit_results.loc[:, date_col] + return max_drawdown_df + + +def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date', + value_col: str = 'profit_ratio' + ): + """ + Calculate max drawdown and the corresponding close dates + :param trades: DataFrame containing trades (requires columns close_date and profit_ratio) + :param date_col: Column in DataFrame to use for dates (defaults to 'close_date') + :param value_col: Column in DataFrame to use for values (defaults to 'profit_ratio') + :return: Tuple (float, highdate, lowdate, highvalue, lowvalue) with absolute max drawdown, + high and low time and high and low value. + :raise: ValueError if trade-dataframe was found empty. + """ + if len(trades) == 0: + raise ValueError("Trade dataframe empty.") + profit_results = trades.sort_values(date_col).reset_index(drop=True) + max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col) + + return max_drawdown_df + + +def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date', + value_col: str = 'profit_abs', starting_balance: float = 0 + ) -> Tuple[float, pd.Timestamp, pd.Timestamp, float, float, float]: + """ + Calculate max drawdown and the corresponding close dates + :param trades: DataFrame containing trades (requires columns close_date and profit_ratio) + :param date_col: Column in DataFrame to use for dates (defaults to 'close_date') + :param value_col: Column in DataFrame to use for values (defaults to 'profit_abs') + :param starting_balance: Portfolio starting balance - properly calculate relative drawdown. + :return: Tuple (float, highdate, lowdate, highvalue, lowvalue, relative_drawdown) + with absolute max drawdown, high and low time and high and low value, + and the relative account drawdown + :raise: ValueError if trade-dataframe was found empty. + """ + if len(trades) == 0: + raise ValueError("Trade dataframe empty.") + profit_results = trades.sort_values(date_col).reset_index(drop=True) + max_drawdown_df = _calc_drawdown_series(profit_results, date_col=date_col, value_col=value_col) + + idxmin = max_drawdown_df['drawdown'].idxmin() + if idxmin == 0: + raise ValueError("No losing trade, therefore no drawdown.") + high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(), date_col] + low_date = profit_results.loc[idxmin, date_col] + high_val = max_drawdown_df.loc[max_drawdown_df.iloc[:idxmin] + ['high_value'].idxmax(), 'cumulative'] + low_val = max_drawdown_df.loc[idxmin, 'cumulative'] + max_drawdown_rel = 0.0 + if high_val + starting_balance != 0: + max_drawdown_rel = (high_val - low_val) / (high_val + starting_balance) + + return ( + abs(min(max_drawdown_df['drawdown'])), + high_date, + low_date, + high_val, + low_val, + max_drawdown_rel + ) + + +def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> Tuple[float, float]: + """ + Calculate min/max cumsum of trades, to show if the wallet/stake amount ratio is sane + :param trades: DataFrame containing trades (requires columns close_date and profit_percent) + :param starting_balance: Add starting balance to results, to show the wallets high / low points + :return: Tuple (float, float) with cumsum of profit_abs + :raise: ValueError if trade-dataframe was found empty. + """ + if len(trades) == 0: + raise ValueError("Trade dataframe empty.") + + csum_df = pd.DataFrame() + csum_df['sum'] = trades['profit_abs'].cumsum() + csum_min = csum_df['sum'].min() + starting_balance + csum_max = csum_df['sum'].max() + starting_balance + + return csum_min, csum_max + + +def calculate_cagr(days_passed: int, starting_balance: float, final_balance: float) -> float: + """ + Calculate CAGR + :param days_passed: Days passed between start and ending balance + :param starting_balance: Starting balance + :param final_balance: Final balance to calculate CAGR against + :return: CAGR + """ + return (final_balance / starting_balance) ** (1 / (days_passed / 365)) - 1 diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py index 846dae9ea..ea6c151e5 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_calmar.py @@ -10,7 +10,7 @@ from typing import Any, Dict from pandas import DataFrame -from freqtrade.data.btanalysis import calculate_max_drawdown +from freqtrade.data.metrics import calculate_max_drawdown from freqtrade.optimize.hyperopt import IHyperOptLoss diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown.py index ce955d928..a8af704cd 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown.py @@ -8,7 +8,7 @@ from datetime import datetime from pandas import DataFrame -from freqtrade.data.btanalysis import calculate_max_drawdown +from freqtrade.data.metrics import calculate_max_drawdown from freqtrade.optimize.hyperopt import IHyperOptLoss diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py index 5bd12ff52..ed689edba 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_profit_drawdown.py @@ -9,7 +9,7 @@ individual needs. """ from pandas import DataFrame -from freqtrade.data.btanalysis import calculate_max_drawdown +from freqtrade.data.metrics import calculate_max_drawdown from freqtrade.optimize.hyperopt import IHyperOptLoss diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 1d58dc339..9c1a276a9 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -9,8 +9,8 @@ from pandas import DataFrame, to_datetime from tabulate import tabulate from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN, UNLIMITED_STAKE_AMOUNT -from freqtrade.data.btanalysis import (calculate_cagr, calculate_csum, calculate_market_change, - calculate_max_drawdown) +from freqtrade.data.metrics import (calculate_cagr, calculate_csum, calculate_market_change, + calculate_max_drawdown) from freqtrade.misc import decimals_per_coin, file_dump_joblib, file_dump_json, round_coin_value from freqtrade.optimize.backtest_caching import get_backtest_metadata_filename diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 5337016f3..773577d7b 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -5,12 +5,13 @@ from typing import Any, Dict, List, Optional import pandas as pd from freqtrade.configuration import TimeRange -from freqtrade.data.btanalysis import (analyze_trade_parallelism, calculate_max_drawdown, - calculate_underwater, combine_dataframes_with_mean, - create_cum_profit, extract_trades_of_period, load_trades) +from freqtrade.data.btanalysis import (analyze_trade_parallelism, extract_trades_of_period, + load_trades) from freqtrade.data.converter import trim_dataframe from freqtrade.data.dataprovider import DataProvider from freqtrade.data.history import get_timerange, load_data +from freqtrade.data.metrics import (calculate_max_drawdown, calculate_underwater, + combine_dataframes_with_mean, create_cum_profit) from freqtrade.enums import CandleType from freqtrade.exceptions import OperationalException from freqtrade.exchange import timeframe_to_prev_date, timeframe_to_seconds diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py index b6ef92bd5..4111b7ff4 100644 --- a/freqtrade/plugins/protections/max_drawdown_protection.py +++ b/freqtrade/plugins/protections/max_drawdown_protection.py @@ -5,7 +5,7 @@ from typing import Any, Dict import pandas as pd -from freqtrade.data.btanalysis import calculate_max_drawdown +from freqtrade.data.metrics import calculate_max_drawdown from freqtrade.persistence import Trade from freqtrade.plugins.protections import IProtection, ProtectionReturn diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index eaf703b2d..f9f49e280 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -8,14 +8,14 @@ from pandas import DataFrame, DateOffset, Timestamp, to_datetime from freqtrade.configuration import TimeRange from freqtrade.constants import LAST_BT_RESULT_FN -from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelism, calculate_cagr, - calculate_csum, calculate_market_change, - calculate_max_drawdown, calculate_underwater, - combine_dataframes_with_mean, create_cum_profit, +from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, analyze_trade_parallelism, extract_trades_of_period, get_latest_backtest_filename, get_latest_hyperopt_file, load_backtest_data, load_backtest_metadata, load_trades, load_trades_from_db) from freqtrade.data.history import load_data, load_pair_history +from freqtrade.data.metrics import (calculate_cagr, calculate_csum, calculate_market_change, + calculate_max_drawdown, calculate_underwater, + combine_dataframes_with_mean, create_cum_profit) from freqtrade.exceptions import OperationalException from tests.conftest import CURRENT_TEST_STRATEGY, create_mock_trades from tests.conftest_trades import MOCK_TRADE_COUNT diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 97f367608..65df2d84c 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -10,7 +10,8 @@ from plotly.subplots import make_subplots from freqtrade.commands import start_plot_dataframe, start_plot_profit from freqtrade.configuration import TimeRange from freqtrade.data import history -from freqtrade.data.btanalysis import create_cum_profit, load_backtest_data +from freqtrade.data.btanalysis import load_backtest_data +from freqtrade.data.metrics import create_cum_profit from freqtrade.exceptions import OperationalException from freqtrade.plot.plotting import (add_areas, add_indicators, add_profit, create_plotconfig, generate_candlestick_graph, generate_plot_filename, From 4580127fa89f27d432177975b10e1b616842d26e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Apr 2022 14:51:57 +0200 Subject: [PATCH 226/254] Small refactor --- freqtrade/optimize/backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 7456c40cf..dd8d82b13 100755 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -1038,7 +1038,7 @@ class Backtesting: timerange: TimeRange): self.progress.init_step(BacktestState.ANALYZE, 0) - logger.info("Running backtesting for Strategy %s", strat.get_strategy_name()) + logger.info(f"Running backtesting for Strategy {strat.get_strategy_name()}") backtest_start_time = datetime.now(timezone.utc) self._set_strategy(strat) From e4df2b0b966961e188664ed92030c9cb6e454f1f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Apr 2022 14:55:52 +0200 Subject: [PATCH 227/254] Revert unwanted changes --- .github/PULL_REQUEST_TEMPLATE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 25a9761e2..90a10d4da 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -9,8 +9,8 @@ Solve the issue: #___ ## Quick changelog -- -- +- +- ## What's new? From 11d447cd5af48048c35ac74b0ea74503dae2adc1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Apr 2022 15:28:01 +0200 Subject: [PATCH 228/254] Add support for download-data "until" --- freqtrade/data/history/history_utils.py | 28 +++++++++++++++------ freqtrade/exchange/binance.py | 4 ++- freqtrade/exchange/exchange.py | 11 ++++++--- tests/data/test_history.py | 33 +++++++++++++++++++++---- 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index 8560fd29e..d4fe6322a 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -139,8 +139,9 @@ def _load_cached_data_for_updating( timeframe: str, timerange: Optional[TimeRange], data_handler: IDataHandler, - candle_type: CandleType -) -> Tuple[DataFrame, Optional[int]]: + candle_type: CandleType, + prepend: bool = False, +) -> Tuple[DataFrame, Optional[int], Optional[int]]: """ Load cached data to download more data. If timerange is passed in, checks whether data from an before the stored data will be @@ -150,9 +151,12 @@ def _load_cached_data_for_updating( Note: Only used by download_pair_history(). """ start = None + end = None if timerange: if timerange.starttype == 'date': start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc) + if timerange.stoptype == 'date': + end = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc) # Intentionally don't pass timerange in - since we need to load the full dataset. data = data_handler.ohlcv_load(pair, timeframe=timeframe, @@ -160,14 +164,18 @@ def _load_cached_data_for_updating( drop_incomplete=True, warn_no_data=False, candle_type=candle_type) if not data.empty: - if start and start < data.iloc[0]['date']: + if not prepend and start and start < data.iloc[0]['date']: # Earlier data than existing data requested, redownload all data = DataFrame(columns=DEFAULT_DATAFRAME_COLUMNS) else: - start = data.iloc[-1]['date'] + if prepend: + end = data.iloc[0]['date'] + else: + start = data.iloc[-1]['date'] start_ms = int(start.timestamp() * 1000) if start else None - return data, start_ms + end_ms = int(end.timestamp() * 1000) if end else None + return data, start_ms, end_ms def _download_pair_history(pair: str, *, @@ -208,9 +216,12 @@ def _download_pair_history(pair: str, *, f'candle type: {candle_type} and store in {datadir}.' ) - data, since_ms = _load_cached_data_for_updating(pair, timeframe, timerange, - data_handler=data_handler, - candle_type=candle_type) + data, since_ms, until_ms = _load_cached_data_for_updating( + pair, timeframe, timerange, + data_handler=data_handler, + candle_type=candle_type, + prepend=False) + # TODO: Prepend should come from a param logger.debug("Current Start: %s", f"{data.iloc[0]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None') @@ -225,6 +236,7 @@ def _download_pair_history(pair: str, *, days=-new_pairs_days).int_timestamp * 1000, is_new_pair=data.empty, candle_type=candle_type, + until_ms=until_ms if until_ms else None ) # TODO: Maybe move parsing to exchange class (?) new_dataframe = ohlcv_to_dataframe(new_data, timeframe, pair, diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 8c442cd26..69ae5198a 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -95,6 +95,7 @@ class Binance(Exchange): async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, since_ms: int, candle_type: CandleType, is_new_pair: bool = False, raise_: bool = False, + until_ms: int = None ) -> Tuple[str, str, str, List]: """ Overwrite to introduce "fast new pair" functionality by detecting the pair's listing date @@ -115,7 +116,8 @@ class Binance(Exchange): since_ms=since_ms, is_new_pair=is_new_pair, raise_=raise_, - candle_type=candle_type + candle_type=candle_type, + until_ms=until_ms, ) def funding_fee_cutoff(self, open_date: datetime): diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 82dcacb51..2ed10ee7a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1645,7 +1645,8 @@ class Exchange: def get_historic_ohlcv(self, pair: str, timeframe: str, since_ms: int, candle_type: CandleType, - is_new_pair: bool = False) -> List: + is_new_pair: bool = False, + until_ms: int = None) -> List: """ Get candle history using asyncio and returns the list of candles. Handles all async work for this. @@ -1653,13 +1654,14 @@ class Exchange: :param pair: Pair to download :param timeframe: Timeframe to get data for :param since_ms: Timestamp in milliseconds to get history from + :param until_ms: Timestamp in milliseconds to get history up to :param candle_type: '', mark, index, premiumIndex, or funding_rate :return: List with candle (OHLCV) data """ pair, _, _, data = self.loop.run_until_complete( self._async_get_historic_ohlcv(pair=pair, timeframe=timeframe, - since_ms=since_ms, is_new_pair=is_new_pair, - candle_type=candle_type)) + since_ms=since_ms, until_ms=until_ms, + is_new_pair=is_new_pair, candle_type=candle_type)) logger.info(f"Downloaded data for {pair} with length {len(data)}.") return data @@ -1680,6 +1682,7 @@ class Exchange: async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, since_ms: int, candle_type: CandleType, is_new_pair: bool = False, raise_: bool = False, + until_ms: int = None ) -> Tuple[str, str, str, List]: """ Download historic ohlcv @@ -1695,7 +1698,7 @@ class Exchange: ) input_coroutines = [self._async_get_candle_history( pair, timeframe, candle_type, since) for since in - range(since_ms, arrow.utcnow().int_timestamp * 1000, one_call)] + range(since_ms, until_ms or (arrow.utcnow().int_timestamp * 1000), one_call)] data: List = [] # Chunk requests into batches of 100 to avoid overwelming ccxt Throttling diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 0585fa0d4..850849da5 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -223,42 +223,65 @@ def test_load_cached_data_for_updating(mocker, testdatadir) -> None: # timeframe starts earlier than the cached data # should fully update data timerange = TimeRange('date', None, test_data[0][0] / 1000 - 1, 0) - data, start_ts = _load_cached_data_for_updating( + data, start_ts, end_ts = _load_cached_data_for_updating( 'UNITTEST/BTC', '1m', timerange, data_handler, CandleType.SPOT) assert data.empty assert start_ts == test_data[0][0] - 1000 + assert end_ts is None + + # timeframe starts earlier than the cached data - prepending + + timerange = TimeRange('date', None, test_data[0][0] / 1000 - 1, 0) + data, start_ts, end_ts = _load_cached_data_for_updating( + 'UNITTEST/BTC', '1m', timerange, data_handler, CandleType.SPOT, True) + assert_frame_equal(data, test_data_df.iloc[:-1]) + assert start_ts == test_data[0][0] - 1000 + assert end_ts == test_data[0][0] # timeframe starts in the center of the cached data # should return the cached data w/o the last item timerange = TimeRange('date', None, test_data[0][0] / 1000 + 1, 0) - data, start_ts = _load_cached_data_for_updating( + data, start_ts, end_ts = _load_cached_data_for_updating( 'UNITTEST/BTC', '1m', timerange, data_handler, CandleType.SPOT) assert_frame_equal(data, test_data_df.iloc[:-1]) assert test_data[-2][0] <= start_ts < test_data[-1][0] + assert end_ts is None # timeframe starts after the cached data # should return the cached data w/o the last item timerange = TimeRange('date', None, test_data[-1][0] / 1000 + 100, 0) - data, start_ts = _load_cached_data_for_updating( + data, start_ts, end_ts = _load_cached_data_for_updating( 'UNITTEST/BTC', '1m', timerange, data_handler, CandleType.SPOT) assert_frame_equal(data, test_data_df.iloc[:-1]) assert test_data[-2][0] <= start_ts < test_data[-1][0] + assert end_ts is None # no datafile exist # should return timestamp start time timerange = TimeRange('date', None, now_ts - 10000, 0) - data, start_ts = _load_cached_data_for_updating( + data, start_ts, end_ts = _load_cached_data_for_updating( 'NONEXIST/BTC', '1m', timerange, data_handler, CandleType.SPOT) assert data.empty assert start_ts == (now_ts - 10000) * 1000 + assert end_ts is None + + # no datafile exist + # should return timestamp start and end time time + timerange = TimeRange('date', 'date', now_ts - 1000000, now_ts - 100000) + data, start_ts, end_ts = _load_cached_data_for_updating( + 'NONEXIST/BTC', '1m', timerange, data_handler, CandleType.SPOT) + assert data.empty + assert start_ts == (now_ts - 1000000) * 1000 + assert end_ts == (now_ts - 100000) * 1000 # no datafile exist, no timeframe is set # should return an empty array and None - data, start_ts = _load_cached_data_for_updating( + data, start_ts, end_ts = _load_cached_data_for_updating( 'NONEXIST/BTC', '1m', None, data_handler, CandleType.SPOT) assert data.empty assert start_ts is None + assert end_ts is None @pytest.mark.parametrize('candle_type,subdir,file_tail', [ From f6a7e6b785ed8f58a4d9d8584ee2e33d3ac2de43 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Apr 2022 17:24:57 +0200 Subject: [PATCH 229/254] Add prepend option to download-data --- docs/data-download.md | 15 ++++++++++++++- freqtrade/commands/arguments.py | 3 ++- freqtrade/commands/cli_options.py | 5 +++++ freqtrade/commands/data_commands.py | 1 + freqtrade/configuration/configuration.py | 2 ++ freqtrade/data/history/history_utils.py | 23 +++++++++++------------ tests/exchange/test_exchange.py | 14 ++++++++++++++ 7 files changed, 49 insertions(+), 14 deletions(-) diff --git a/docs/data-download.md b/docs/data-download.md index 9bfc1e685..c1caa8722 100644 --- a/docs/data-download.md +++ b/docs/data-download.md @@ -30,6 +30,7 @@ usage: freqtrade download-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] [--data-format-ohlcv {json,jsongz,hdf5}] [--data-format-trades {json,jsongz,hdf5}] [--trading-mode {spot,margin,futures}] + [--prepend] optional arguments: -h, --help show this help message and exit @@ -62,6 +63,7 @@ optional arguments: `jsongz`). --trading-mode {spot,margin,futures} Select Trading mode + --prepend Allow data prepending. Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). @@ -157,10 +159,21 @@ freqtrade download-data --exchange binance --pairs .*/USDT - To change the exchange used to download the historical data from, please use a different configuration file (you'll probably need to adjust rate limits etc.) - To use `pairs.json` from some other directory, use `--pairs-file some_other_dir/pairs.json`. - To download historical candle (OHLCV) data for only 10 days, use `--days 10` (defaults to 30 days). -- To download historical candle (OHLCV) data from a fixed starting point, use `--timerange 20200101-` - which will download all data from January 1st, 2020. Eventually set end dates are ignored. +- To download historical candle (OHLCV) data from a fixed starting point, use `--timerange 20200101-` - which will download all data from January 1st, 2020. - Use `--timeframes` to specify what timeframe download the historical candle (OHLCV) data for. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute data. - To use exchange, timeframe and list of pairs as defined in your configuration file, use the `-c/--config` option. With this, the script uses the whitelist defined in the config as the list of currency pairs to download data for and does not require the pairs.json file. You can combine `-c/--config` with most other options. +#### Download additional data before the current timerange + +Assuming you downloaded all data from 2022 (`--timerange 20220101-`) - but you'd now like to also backtest with earlier data. +You can do so by using the `--prepend` flag, combined with + +``` bash +freqtrade download-data --exchange binance --pairs ETH/USDT XRP/USDT BTC/USDT --prepend --timerange 20210101-20220101 +``` + +!!! Note + Freqtrade will ignore the end-date in this mode if data is available, updating the end-date to the existing data start point. ### Data format diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 62b79da2e..ff1d16590 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -72,7 +72,8 @@ ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs", "trading_mode"] ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "new_pairs_days", "include_inactive", "timerange", "download_trades", "exchange", "timeframes", - "erase", "dataformat_ohlcv", "dataformat_trades", "trading_mode"] + "erase", "dataformat_ohlcv", "dataformat_trades", "trading_mode", + "prepend_data"] ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit", "db_url", "trade_source", "export", "exportfilename", diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index df8966e85..58e208652 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -443,6 +443,11 @@ AVAILABLE_CLI_OPTIONS = { default=['1m', '5m'], nargs='+', ), + "prepend_data": Arg( + '--prepend', + help='Allow data prepending.', + action='store_true', + ), "erase": Arg( '--erase', help='Clean all existing data for the selected exchange/pairs/timeframes.', diff --git a/freqtrade/commands/data_commands.py b/freqtrade/commands/data_commands.py index e41512ccc..a2e2a100a 100644 --- a/freqtrade/commands/data_commands.py +++ b/freqtrade/commands/data_commands.py @@ -85,6 +85,7 @@ def start_download_data(args: Dict[str, Any]) -> None: new_pairs_days=config['new_pairs_days'], erase=bool(config.get('erase')), data_format=config['dataformat_ohlcv'], trading_mode=config.get('trading_mode', 'spot'), + prepend=config.get('prepend_data', False) ) except KeyboardInterrupt: diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index dde56c220..80df6fb3f 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -393,6 +393,8 @@ class Configuration: self._args_to_config(config, argname='trade_source', logstring='Using trades from: {}') + self._args_to_config(config, argname='prepend_data', + logstring='Prepend detected. Allowing data prepending.') self._args_to_config(config, argname='erase', logstring='Erase detected. Deleting existing data.') diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index d4fe6322a..f1304607b 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -172,7 +172,6 @@ def _load_cached_data_for_updating( end = data.iloc[0]['date'] else: start = data.iloc[-1]['date'] - start_ms = int(start.timestamp() * 1000) if start else None end_ms = int(end.timestamp() * 1000) if end else None return data, start_ms, end_ms @@ -188,6 +187,7 @@ def _download_pair_history(pair: str, *, timerange: Optional[TimeRange] = None, candle_type: CandleType, erase: bool = False, + prepend: bool = False, ) -> bool: """ Download latest candles from the exchange for the pair and timeframe passed in parameters @@ -195,8 +195,6 @@ def _download_pair_history(pair: str, *, exists in a cache. If timerange starts earlier than the data in the cache, the full data will be redownloaded - Based on @Rybolov work: https://github.com/rybolov/freqtrade-data - :param pair: pair to download :param timeframe: Timeframe (e.g "5m") :param timerange: range of time to download @@ -211,17 +209,17 @@ def _download_pair_history(pair: str, *, if data_handler.ohlcv_purge(pair, timeframe, candle_type=candle_type): logger.info(f'Deleting existing data for pair {pair}, {timeframe}, {candle_type}.') - logger.info( - f'Download history data for pair: "{pair}" ({process}), timeframe: {timeframe}, ' - f'candle type: {candle_type} and store in {datadir}.' - ) - data, since_ms, until_ms = _load_cached_data_for_updating( pair, timeframe, timerange, data_handler=data_handler, candle_type=candle_type, - prepend=False) - # TODO: Prepend should come from a param + prepend=prepend) + + logger.info(f'Download history data for "{pair}" ({process}), timeframe: {timeframe}, ' + f'candle type: {candle_type} and store in {datadir}.' + f'From {format_ms_time(since_ms) if since_ms else "start"} to ' + f'{format_ms_time(until_ms) if until_ms else "now"}' + ) logger.debug("Current Start: %s", f"{data.iloc[0]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None') @@ -269,6 +267,7 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes timerange: Optional[TimeRange] = None, new_pairs_days: int = 30, erase: bool = False, data_format: str = None, + prepend: bool = False, ) -> List[str]: """ Refresh stored ohlcv data for backtesting and hyperopt operations. @@ -292,7 +291,7 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes timerange=timerange, data_handler=data_handler, timeframe=str(timeframe), new_pairs_days=new_pairs_days, candle_type=candle_type, - erase=erase) + erase=erase, prepend=prepend) if trading_mode == 'futures': # Predefined candletype (and timeframe) depending on exchange # Downloads what is necessary to backtest based on futures data. @@ -306,7 +305,7 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes timerange=timerange, data_handler=data_handler, timeframe=str(tf_mark), new_pairs_days=new_pairs_days, candle_type=funding_candle_type, - erase=erase) + erase=erase, prepend=prepend) return pairs_not_available diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 689ffa4ce..31311cc38 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1983,6 +1983,20 @@ async def test__async_get_historic_ohlcv(default_conf, mocker, caplog, exchange_ assert exchange._api_async.fetch_ohlcv.call_count > 200 assert res[0] == ohlcv[0] + exchange._api_async.fetch_ohlcv.reset_mock() + end_ts = 1_500_500_000_000 + start_ts = 1_500_000_000_000 + respair, restf, _, res = await exchange._async_get_historic_ohlcv( + pair, "5m", since_ms=start_ts, candle_type=candle_type, is_new_pair=False, + until_ms=end_ts + ) + # Required candles + candles = (end_ts - start_ts) / 300_000 + exp = candles // exchange.ohlcv_candle_limit('5m') + 1 + + # Depending on the exchange, this should be called between 1 and 6 times. + assert exchange._api_async.fetch_ohlcv.call_count == exp + @pytest.mark.parametrize('candle_type', [CandleType.FUTURES, CandleType.MARK, CandleType.SPOT]) def test_refresh_latest_ohlcv(mocker, default_conf, caplog, candle_type) -> None: From e49b3ef051a3fa0b710a6288bf36ae6d51898dba Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Apr 2022 17:35:11 +0200 Subject: [PATCH 230/254] Improve message formatting --- freqtrade/data/history/history_utils.py | 4 ++-- tests/data/test_history.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index f1304607b..af3a39277 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -215,8 +215,8 @@ def _download_pair_history(pair: str, *, candle_type=candle_type, prepend=prepend) - logger.info(f'Download history data for "{pair}" ({process}), timeframe: {timeframe}, ' - f'candle type: {candle_type} and store in {datadir}.' + logger.info(f'({process}) - Download history data for "{pair}", {timeframe}, ' + f'{candle_type} and store in {datadir}.' f'From {format_ms_time(since_ms) if since_ms else "start"} to ' f'{format_ms_time(until_ms) if until_ms else "now"}' ) diff --git a/tests/data/test_history.py b/tests/data/test_history.py index 850849da5..82d4a841c 100644 --- a/tests/data/test_history.py +++ b/tests/data/test_history.py @@ -149,8 +149,8 @@ def test_load_data_with_new_pair_1min(ohlcv_history_list, mocker, caplog, load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC', candle_type=candle_type) assert file.is_file() assert log_has_re( - r'Download history data for pair: "MEME/BTC" \(0/1\), timeframe: 1m, ' - r'candle type: spot and store in .*', caplog + r'\(0/1\) - Download history data for "MEME/BTC", 1m, ' + r'spot and store in .*', caplog ) From 8b5d454b50046edfd09d2dbf92d226fbf14346f6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Apr 2022 17:44:57 +0200 Subject: [PATCH 231/254] Fix subtle bug in trades download --- freqtrade/data/history/history_utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py index af3a39277..eb36d2042 100644 --- a/freqtrade/data/history/history_utils.py +++ b/freqtrade/data/history/history_utils.py @@ -323,8 +323,9 @@ def _download_trades_history(exchange: Exchange, try: until = None - if (timerange and timerange.starttype == 'date'): - since = timerange.startts * 1000 + if timerange: + if timerange.starttype == 'date': + since = timerange.startts * 1000 if timerange.stoptype == 'date': until = timerange.stopts * 1000 else: From 5c1ac3cf9503ca86d7e52f3f5f199fb356a647fc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Apr 2022 19:55:13 +0200 Subject: [PATCH 232/254] Fix caching bug with freqUI backtesting --- freqtrade/rpc/api_server/api_backtest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index a902ea984..41712632b 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -84,6 +84,7 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac lastconfig['enable_protections'] = btconfig.get('enable_protections') lastconfig['dry_run_wallet'] = btconfig.get('dry_run_wallet') + ApiServer._bt.strategylist = [strat] ApiServer._bt.results = {} ApiServer._bt.load_prior_backtest() From 0c921e01161a72c318f1e8a4b0049a2f77f08be0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Apr 2022 20:08:40 +0200 Subject: [PATCH 233/254] Reorder api_backtesting test sequence --- tests/rpc/test_rpc_apiserver.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 43f783a53..ac2f1c3ec 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1483,7 +1483,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): assert not result['running'] assert result['status_msg'] == 'Backtest reset' ftbot.config['export'] = 'trades' - ftbot.config['backtest_cache'] = 'none' + ftbot.config['backtest_cache'] = 'day' ftbot.config['user_data_dir'] = Path(tmpdir) ftbot.config['exportfilename'] = Path(tmpdir) / "backtest_results" ftbot.config['exportfilename'].mkdir() @@ -1556,19 +1556,19 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): ApiServer._bgtask_running = False - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy', - side_effect=DependencyException()) - rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data)) - assert log_has("Backtesting caused an error: ", caplog) - - ftbot.config['backtest_cache'] = 'day' - # Rerun backtest (should get previous result) rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data)) assert_response(rc) result = rc.json() assert log_has_re('Reusing result of previous backtest.*', caplog) + data['stake_amount'] = 101 + + mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy', + side_effect=DependencyException()) + rc = client_post(client, f"{BASE_URI}/backtest", data=json.dumps(data)) + assert log_has("Backtesting caused an error: ", caplog) + # Delete backtesting to avoid leakage since the backtest-object may stick around. rc = client_delete(client, f"{BASE_URI}/backtest") assert_response(rc) From d5fc923dcbe02daae51006494be03be6d68a874c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 May 2022 09:53:34 +0200 Subject: [PATCH 234/254] Properly validate stoploss existence for optimize commands closes #6740 --- freqtrade/configuration/config_setup.py | 2 +- freqtrade/configuration/config_validation.py | 11 +++++++---- freqtrade/constants.py | 4 ++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/freqtrade/configuration/config_setup.py b/freqtrade/configuration/config_setup.py index 02f2d4089..d49bf61f6 100644 --- a/freqtrade/configuration/config_setup.py +++ b/freqtrade/configuration/config_setup.py @@ -22,6 +22,6 @@ def setup_utils_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str # Ensure these modes are using Dry-run config['dry_run'] = True - validate_config_consistency(config) + validate_config_consistency(config, preliminary=True) return config diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 6e4a4b0ef..ee846e7e6 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -39,7 +39,7 @@ def _extend_validator(validator_class): FreqtradeValidator = _extend_validator(Draft4Validator) -def validate_config_schema(conf: Dict[str, Any]) -> Dict[str, Any]: +def validate_config_schema(conf: Dict[str, Any], preliminary: bool = False) -> Dict[str, Any]: """ Validate the configuration follow the Config Schema :param conf: Config in JSON format @@ -49,7 +49,10 @@ def validate_config_schema(conf: Dict[str, Any]) -> Dict[str, Any]: if conf.get('runmode', RunMode.OTHER) in (RunMode.DRY_RUN, RunMode.LIVE): conf_schema['required'] = constants.SCHEMA_TRADE_REQUIRED elif conf.get('runmode', RunMode.OTHER) in (RunMode.BACKTEST, RunMode.HYPEROPT): - conf_schema['required'] = constants.SCHEMA_BACKTEST_REQUIRED + if preliminary: + conf_schema['required'] = constants.SCHEMA_BACKTEST_REQUIRED + else: + conf_schema['required'] = constants.SCHEMA_BACKTEST_REQUIRED_FINAL else: conf_schema['required'] = constants.SCHEMA_MINIMAL_REQUIRED try: @@ -64,7 +67,7 @@ def validate_config_schema(conf: Dict[str, Any]) -> Dict[str, Any]: ) -def validate_config_consistency(conf: Dict[str, Any]) -> None: +def validate_config_consistency(conf: Dict[str, Any], preliminary: bool = False) -> None: """ Validate the configuration consistency. Should be ran after loading both configuration and strategy, @@ -85,7 +88,7 @@ def validate_config_consistency(conf: Dict[str, Any]) -> None: # validate configuration before returning logger.info('Validating configuration ...') - validate_config_schema(conf) + validate_config_schema(conf, preliminary=preliminary) def _validate_unlimited_amount(conf: Dict[str, Any]) -> None: diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 1a21ec77f..0ceabe917 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -462,6 +462,10 @@ SCHEMA_BACKTEST_REQUIRED = [ 'dataformat_ohlcv', 'dataformat_trades', ] +SCHEMA_BACKTEST_REQUIRED_FINAL = SCHEMA_BACKTEST_REQUIRED + [ + 'stoploss', + 'minimal_roi', +] SCHEMA_MINIMAL_REQUIRED = [ 'exchange', From 2cedbe5704b9d28facfd4976d0848fec8f523aa3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 1 May 2022 14:50:36 +0200 Subject: [PATCH 235/254] Fix documentation mishap --- docs/data-download.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data-download.md b/docs/data-download.md index c1caa8722..681fb717d 100644 --- a/docs/data-download.md +++ b/docs/data-download.md @@ -166,7 +166,7 @@ freqtrade download-data --exchange binance --pairs .*/USDT #### Download additional data before the current timerange Assuming you downloaded all data from 2022 (`--timerange 20220101-`) - but you'd now like to also backtest with earlier data. -You can do so by using the `--prepend` flag, combined with +You can do so by using the `--prepend` flag, combined with `--timerange` - specifying an end-date. ``` bash freqtrade download-data --exchange binance --pairs ETH/USDT XRP/USDT BTC/USDT --prepend --timerange 20210101-20220101 From f9244aad92928d941507ccba73bcf184b5942e6c Mon Sep 17 00:00:00 2001 From: Nicolas Papp Date: Sun, 1 May 2022 12:25:53 -0300 Subject: [PATCH 236/254] Fix on max drawdown formula to match tests --- docs/hyperopt.md | 2 +- freqtrade/data/metrics.py | 2 +- tests/data/test_btanalysis.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index bab062fad..030d73f4b 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -566,7 +566,7 @@ Currently, the following loss functions are builtin: * `SortinoHyperOptLoss` - optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation. * `SortinoHyperOptLossDaily` - optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation. * `MaxDrawDownHyperOptLoss` - Optimizes Maximum absolute drawdown. -* `MaxDrawDownRelativeHyperOptLoss` - Similar as the above, but also optimizes Maximum relative drawdown. +* `MaxDrawDownRelativeHyperOptLoss` - Optimizes both maximum absolute drawdown while also adjusting for maximum relative drawdown. * `CalmarHyperOptLoss` - Optimizes Calmar Ratio calculated on trade returns relative to max drawdown. * `ProfitDrawDownHyperOptLoss` - Optimizes by max Profit & min Drawdown objective. `DRAWDOWN_MULT` variable within the hyperoptloss file can be adjusted to be stricter or more flexible on drawdown purposes. diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index 5e93ae0dc..79d192f83 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -153,7 +153,7 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date' max_drawdown_rel = max_drawdown_df.loc[idxmin, 'drawdown_relative'] return ( - abs(min(max_drawdown_df['drawdown'])), + abs(max_drawdown_df.loc[idxmin, 'drawdown']), high_date, low_date, high_val, diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 2cfc33b6b..4157bd899 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -380,7 +380,7 @@ def test_calculate_max_drawdown2(): @pytest.mark.parametrize('profits,relative,highd,lowd,result,result_rel', [ ([0.0, -500.0, 500.0, 10000.0, -1000.0], False, 3, 4, 1000.0, 0.090909), - ([0.0, -500.0, 500.0, 10000.0, -1000.0], True, 0, 1, 1000.0, 0.5), + ([0.0, -500.0, 500.0, 10000.0, -1000.0], True, 0, 1, 500.0, 0.5), ]) def test_calculate_max_drawdown_abs(profits, relative, highd, lowd, result, result_rel): From 7160f9085a8a40dba180b0f4098d6d13e0e65fc0 Mon Sep 17 00:00:00 2001 From: Nicolas Papp Date: Sun, 1 May 2022 12:32:12 -0300 Subject: [PATCH 237/254] Update summary examples --- docs/backtesting.md | 85 ++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index d925e0e4e..3b1f940fb 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -377,50 +377,47 @@ The last element of the backtest report is the summary metrics table. It contains some useful key metrics about performance of your strategy on backtesting data. ``` -================ SUMMARY METRICS =============== -| Metric | Value | -|------------------------+---------------------| -| Backtesting from | 2019-01-01 00:00:00 | -| Backtesting to | 2019-05-01 00:00:00 | -| Max open trades | 3 | -| | | -| Total/Daily Avg Trades | 429 / 3.575 | -| Starting balance | 0.01000000 BTC | -| Final balance | 0.01762792 BTC | -| Absolute profit | 0.00762792 BTC | -| Total profit % | 76.2% | -| CAGR % | 460.87% | -| Avg. stake amount | 0.001 BTC | -| Total trade volume | 0.429 BTC | -| | | -| Long / Short | 352 / 77 | -| Total profit Long % | 1250.58% | -| Total profit Short % | -15.02% | -| Absolute profit Long | 0.00838792 BTC | -| Absolute profit Short | -0.00076 BTC | -| | | -| Best Pair | LSK/BTC 26.26% | -| Worst Pair | ZEC/BTC -10.18% | -| Best Trade | LSK/BTC 4.25% | -| Worst Trade | ZEC/BTC -10.25% | -| Best day | 0.00076 BTC | -| Worst day | -0.00036 BTC | -| Days win/draw/lose | 12 / 82 / 25 | -| Avg. Duration Winners | 4:23:00 | -| Avg. Duration Loser | 6:55:00 | -| Rejected Entry signals | 3089 | -| Entry/Exit Timeouts | 0 / 0 | -| | | -| Min balance | 0.00945123 BTC | -| Max balance | 0.01846651 BTC | -| Drawdown (Account) | 13.33% | -| Drawdown | 0.0015 BTC | -| Drawdown high | 0.0013 BTC | -| Drawdown low | -0.0002 BTC | -| Drawdown Start | 2019-02-15 14:10:00 | -| Drawdown End | 2019-04-11 18:15:00 | -| Market change | -5.88% | -================================================ +================== SUMMARY METRICS ================== +| Metric | Value | +|-----------------------------+---------------------| +| Backtesting from | 2022-02-01 00:00:00 | +| Backtesting to | 2022-03-15 00:15:00 | +| Max open trades | 10 | +| | | +| Total/Daily Avg Trades | 77 / 1.83 | +| Starting balance | 1000 USDT | +| Final balance | 1135.843 USDT | +| Absolute profit | 135.843 USDT | +| Total profit % | 13.58% | +| CAGR % | 202.51% | +| Trades per day | 1.83 | +| Avg. daily profit % | 0.32% | +| Avg. stake amount | 105.996 USDT | +| Total trade volume | 8161.695 USDT | +| | | +| Best Pair | THETA/USDT 61.28% | +| Worst Pair | SAND/USDT -15.57% | +| Best trade | THETA/USDT 25.47% | +| Worst trade | SAND/USDT -5.19% | +| Best day | 73.347 USDT | +| Worst day | -56.261 USDT | +| Days win/draw/lose | 12 / 9 / 11 | +| Avg. Duration Winners | 1 day, 19:30:00 | +| Avg. Duration Loser | 20:31:00 | +| Rejected Entry signals | 16959 | +| Entry/Exit Timeouts | 0 / 0 | +| | | +| Min balance | 970.12 USDT | +| Max balance | 1141.775 USDT | +| Max % of account underwater | 7.07% | +| Absolute Drawdown (Account) | 7.07% | +| Absolute Drawdown | 77.666 USDT | +| Drawdown high | 97.995 USDT | +| Drawdown low | 20.329 USDT | +| Drawdown Start | 2022-02-11 08:00:00 | +| Drawdown End | 2022-02-13 15:30:00 | +| Market change | -6.67% | +===================================================== ``` From a0e27d82aadf784ab36fdf9b027a1b2aa12e0698 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 May 2022 03:01:00 +0000 Subject: [PATCH 238/254] Bump types-python-dateutil from 2.8.12 to 2.8.14 Bumps [types-python-dateutil](https://github.com/python/typeshed) from 2.8.12 to 2.8.14. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-python-dateutil dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c4fe366a5..35f4ba47c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -26,4 +26,4 @@ types-cachetools==5.0.1 types-filelock==3.2.5 types-requests==2.27.20 types-tabulate==0.8.7 -types-python-dateutil==2.8.12 +types-python-dateutil==2.8.14 From 3d730661ee9ccfae9d8670b09eaf11b9403b205b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 May 2022 03:01:11 +0000 Subject: [PATCH 239/254] Bump mkdocs-material from 8.2.10 to 8.2.12 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.2.10 to 8.2.12. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/8.2.10...8.2.12) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 97be17243..cddde4789 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,5 +1,5 @@ mkdocs==1.3.0 -mkdocs-material==8.2.10 +mkdocs-material==8.2.12 mdx_truly_sane_lists==1.2 pymdown-extensions==9.4 jinja2==3.1.1 From 4990534bf41988dde6b1d9e5568764ad12c7a30b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 May 2022 03:01:22 +0000 Subject: [PATCH 240/254] Bump mypy from 0.942 to 0.950 Bumps [mypy](https://github.com/python/mypy) from 0.942 to 0.950. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.942...v0.950) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c4fe366a5..d793d3239 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ coveralls==3.3.1 flake8==4.0.1 flake8-tidy-imports==4.6.0 -mypy==0.942 +mypy==0.950 pre-commit==2.18.1 pytest==7.1.2 pytest-asyncio==0.18.3 From 73aafb886b15d16da222665bd5088ad028ec0e95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 May 2022 03:01:33 +0000 Subject: [PATCH 241/254] Bump ccxt from 1.80.61 to 1.81.16 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.80.61 to 1.81.16. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.80.61...1.81.16) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ab8329979..193be1e85 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.22.3 pandas==1.4.2 pandas-ta==0.3.14b -ccxt==1.80.61 +ccxt==1.81.16 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.2 aiohttp==3.8.1 From 093bea4230e279c8596bc89cbbd9a74b25708d72 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 May 2022 03:01:34 +0000 Subject: [PATCH 242/254] Bump types-requests from 2.27.20 to 2.27.25 Bumps [types-requests](https://github.com/python/typeshed) from 2.27.20 to 2.27.25. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c4fe366a5..7f5ba3370 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -24,6 +24,6 @@ nbconvert==6.5.0 # mypy types types-cachetools==5.0.1 types-filelock==3.2.5 -types-requests==2.27.20 +types-requests==2.27.25 types-tabulate==0.8.7 types-python-dateutil==2.8.12 From 9de0652b2cbad776466ebb521a0122720bc692d9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 May 2022 04:31:34 +0000 Subject: [PATCH 243/254] Bump jinja2 from 3.1.1 to 3.1.2 Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.1 to 3.1.2. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.1.1...3.1.2) --- updated-dependencies: - dependency-name: jinja2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index cddde4789..c5a4b64b3 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -2,4 +2,4 @@ mkdocs==1.3.0 mkdocs-material==8.2.12 mdx_truly_sane_lists==1.2 pymdown-extensions==9.4 -jinja2==3.1.1 +jinja2==3.1.2 diff --git a/requirements.txt b/requirements.txt index 193be1e85..472d207f0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ TA-Lib==0.4.24 technical==1.3.0 tabulate==0.8.9 pycoingecko==2.2.0 -jinja2==3.1.1 +jinja2==3.1.2 tables==3.7.0 blosc==1.10.6 joblib==1.1.0 From ba28fa6c3cec0ffa6f2bc4842ff8ec475b3e75a9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 May 2022 04:31:35 +0000 Subject: [PATCH 244/254] Bump cryptography from 36.0.2 to 37.0.1 Bumps [cryptography](https://github.com/pyca/cryptography) from 36.0.2 to 37.0.1. - [Release notes](https://github.com/pyca/cryptography/releases) - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/36.0.2...37.0.1) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 193be1e85..7162dca38 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ pandas-ta==0.3.14b ccxt==1.81.16 # Pin cryptography for now due to rust build errors with piwheels -cryptography==36.0.2 +cryptography==37.0.1 aiohttp==3.8.1 SQLAlchemy==1.4.35 python-telegram-bot==13.11 From 49c1b310c2d35bca329eb51021f8de2d446e3f80 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 May 2022 06:44:30 +0200 Subject: [PATCH 245/254] Bump pre-commit types --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2170b704a..150a665b5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: additional_dependencies: - types-cachetools==5.0.1 - types-filelock==3.2.5 - - types-requests==2.27.20 + - types-requests==2.27.25 - types-tabulate==0.8.7 - types-python-dateutil==2.8.12 # stages: [push] From 71ae92274d849c9f57722018460d0cdae4c54c40 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 May 2022 06:45:42 +0200 Subject: [PATCH 246/254] Bump pre-commit dependency --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2170b704a..79ed17c7c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - types-filelock==3.2.5 - types-requests==2.27.20 - types-tabulate==0.8.7 - - types-python-dateutil==2.8.12 + - types-python-dateutil==2.8.14 # stages: [push] - repo: https://github.com/pycqa/isort From 67dd9be95a16be0a77d33a7b768880208534e646 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 May 2022 05:00:17 +0000 Subject: [PATCH 247/254] Bump sqlalchemy from 1.4.35 to 1.4.36 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.4.35 to 1.4.36. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: sqlalchemy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 7162dca38..6972606aa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ ccxt==1.81.16 # Pin cryptography for now due to rust build errors with piwheels cryptography==37.0.1 aiohttp==3.8.1 -SQLAlchemy==1.4.35 +SQLAlchemy==1.4.36 python-telegram-bot==13.11 arrow==1.2.2 cachetools==4.2.2 From 24ce90ba9b36697da7d03c755add3a2a0ebed980 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 May 2022 06:00:02 +0000 Subject: [PATCH 248/254] Bump types-tabulate from 0.8.7 to 0.8.8 Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.7 to 0.8.8. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-tabulate dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c2e630c7e..151200982 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -25,5 +25,5 @@ nbconvert==6.5.0 types-cachetools==5.0.1 types-filelock==3.2.5 types-requests==2.27.25 -types-tabulate==0.8.7 +types-tabulate==0.8.8 types-python-dateutil==2.8.14 From 38dffe1ed675f6b4844704304ab716e82b370072 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 May 2022 08:11:05 +0200 Subject: [PATCH 249/254] types-tabulate - pre-commit update --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 335b11f12..1185028b9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - types-cachetools==5.0.1 - types-filelock==3.2.5 - types-requests==2.27.25 - - types-tabulate==0.8.7 + - types-tabulate==0.8.8 - types-python-dateutil==2.8.14 # stages: [push] From 2a6efab8a2c0d63a28cde675da0cac7ab1c69652 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 May 2022 07:16:10 +0200 Subject: [PATCH 250/254] Don't use deprecated abstractclassmethod decorator --- freqtrade/data/history/idatahandler.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/freqtrade/data/history/idatahandler.py b/freqtrade/data/history/idatahandler.py index 4a5eb6bc2..2e6b070ca 100644 --- a/freqtrade/data/history/idatahandler.py +++ b/freqtrade/data/history/idatahandler.py @@ -5,7 +5,7 @@ It's subclasses handle and storing data from disk. """ import logging import re -from abc import ABC, abstractclassmethod, abstractmethod +from abc import ABC, abstractmethod from copy import deepcopy from datetime import datetime, timezone from pathlib import Path @@ -38,7 +38,8 @@ class IDataHandler(ABC): """ raise NotImplementedError() - @abstractclassmethod + @classmethod + @abstractmethod def ohlcv_get_available_data( cls, datadir: Path, trading_mode: TradingMode) -> ListPairsWithTimeframes: """ @@ -48,7 +49,8 @@ class IDataHandler(ABC): :return: List of Tuples of (pair, timeframe) """ - @abstractclassmethod + @classmethod + @abstractmethod def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> List[str]: """ Returns a list of all pairs with ohlcv data available in this datadir @@ -118,7 +120,8 @@ class IDataHandler(ABC): :param candle_type: Any of the enum CandleType (must match trading mode!) """ - @abstractclassmethod + @classmethod + @abstractmethod def trades_get_pairs(cls, datadir: Path) -> List[str]: """ Returns a list of all pairs for which trade data is available in this From 1e2523af612c4dae3613f8f8df860d0a5a849c78 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 May 2022 19:44:14 +0200 Subject: [PATCH 251/254] Fix some assumptions on the data available_capital is not guaranteed to be available, while dry-run-wallet is. --- freqtrade/data/metrics.py | 6 +++--- .../hyperopt_loss/hyperopt_loss_max_drawdown_relative.py | 2 +- freqtrade/plot/plotting.py | 3 ++- tests/test_plotting.py | 1 - 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index 79d192f83..c11a2df88 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -1,5 +1,5 @@ import logging -from typing import Dict, Optional, Tuple +from typing import Dict, Tuple import numpy as np import pandas as pd @@ -73,7 +73,7 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str, def _calc_drawdown_series(profit_results: pd.DataFrame, *, date_col: str, value_col: str, - starting_balance: Optional[float] = 0.0) -> pd.DataFrame: + starting_balance: float) -> pd.DataFrame: max_drawdown_df = pd.DataFrame() max_drawdown_df['cumulative'] = profit_results[value_col].cumsum() max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax() @@ -93,7 +93,7 @@ def _calc_drawdown_series(profit_results: pd.DataFrame, *, date_col: str, value_ def calculate_underwater(trades: pd.DataFrame, *, date_col: str = 'close_date', - value_col: str = 'profit_ratio', starting_balance: Optional[float] = 0.0 + value_col: str = 'profit_ratio', starting_balance: float = 0.0 ): """ Calculate max drawdown and the corresponding close dates diff --git a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py index 393aaa2c8..3182afb47 100644 --- a/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py +++ b/freqtrade/optimize/hyperopt_loss/hyperopt_loss_max_drawdown_relative.py @@ -36,7 +36,7 @@ class MaxDrawDownRelativeHyperOptLoss(IHyperOptLoss): drawdown_df = calculate_underwater( results, value_col='profit_abs', - starting_balance=config['available_capital'] + starting_balance=config['dry_run_wallet'] ) max_drawdown = abs(min(drawdown_df['drawdown'])) relative_drawdown = max(drawdown_df['drawdown_relative']) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 3a4eaf4f4..37758d05f 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -692,7 +692,8 @@ def plot_profit(config: Dict[str, Any]) -> None: # this could be useful to gauge the overall market trend fig = generate_profit_graph(plot_elements['pairs'], plot_elements['ohlcv'], trades, config['timeframe'], - config.get('stake_currency', ''), config['available_capital']) + config.get('stake_currency', ''), + config.get('available_capital', config['dry_run_wallet'])) store_plot_file(fig, filename='freqtrade-profit-plot.html', directory=config['user_data_dir'] / 'plot', auto_open=config.get('plot_auto_open', False)) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 630007352..9ee7a75c6 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -454,7 +454,6 @@ def test_plot_profit(default_conf, mocker, testdatadir): default_conf['datadir'] = testdatadir default_conf['exportfilename'] = testdatadir / 'backtest-result_test_nofile.json' default_conf['pairs'] = ['ETH/BTC', 'LTC/BTC'] - default_conf['available_capital'] = 1000 profit_mock = MagicMock() store_mock = MagicMock() From 3f64c6307fe92a96d5a08f8e535b125960c98c8a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 May 2022 20:01:44 +0200 Subject: [PATCH 252/254] Maintain compatibility with old backtest results --- freqtrade/optimize/optimize_reports.py | 34 ++++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index e65fd6498..42db366a1 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -733,6 +733,26 @@ def text_table_add_metrics(strat_results: Dict) -> str: strat_results['stake_currency'])), ] if strat_results.get('trade_count_short', 0) > 0 else [] + drawdown_metrics = [] + if 'max_relative_drawdown' in strat_results: + # Compatibility to show old hyperopt results + drawdown_metrics.append( + ('Max % of account underwater', f"{strat_results['max_relative_drawdown']:.2%}") + ) + drawdown_metrics.extend([ + ('Absolute Drawdown (Account)', f"{strat_results['max_drawdown_account']:.2%}") + if 'max_drawdown_account' in strat_results else ( + 'Drawdown', f"{strat_results['max_drawdown']:.2%}"), + ('Absolute Drawdown', round_coin_value(strat_results['max_drawdown_abs'], + strat_results['stake_currency'])), + ('Drawdown high', round_coin_value(strat_results['max_drawdown_high'], + strat_results['stake_currency'])), + ('Drawdown low', round_coin_value(strat_results['max_drawdown_low'], + strat_results['stake_currency'])), + ('Drawdown Start', strat_results['drawdown_start']), + ('Drawdown End', strat_results['drawdown_end']), + ]) + # Newly added fields should be ignored if they are missing in strat_results. hyperopt-show # command stores these results and newer version of freqtrade must be able to handle old # results with missing new fields. @@ -788,19 +808,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: ('Max balance', round_coin_value(strat_results['csum_max'], strat_results['stake_currency'])), - # Compatibility to show old hyperopt results - ('Max % of account underwater', f"{strat_results['max_relative_drawdown']:.2%}"), - ('Absolute Drawdown (Account)', f"{strat_results['max_drawdown_account']:.2%}") - if 'max_drawdown_account' in strat_results else ( - 'Drawdown', f"{strat_results['max_drawdown']:.2%}"), - ('Absolute Drawdown', round_coin_value(strat_results['max_drawdown_abs'], - strat_results['stake_currency'])), - ('Drawdown high', round_coin_value(strat_results['max_drawdown_high'], - strat_results['stake_currency'])), - ('Drawdown low', round_coin_value(strat_results['max_drawdown_low'], - strat_results['stake_currency'])), - ('Drawdown Start', strat_results['drawdown_start']), - ('Drawdown End', strat_results['drawdown_end']), + *drawdown_metrics, ('Market change', f"{strat_results['market_change']:.2%}"), ] From 7a5762991884af58ba3367299409d65469a27344 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 2 May 2022 20:08:38 +0200 Subject: [PATCH 253/254] Keep Backtest-metrics aligned --- docs/backtesting.md | 154 +++++++++++++++++++++++--------------------- 1 file changed, 82 insertions(+), 72 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 3b1f940fb..75225b654 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -287,45 +287,51 @@ A backtesting result will look like that: | ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 | | LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 | | TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 | -================ SUMMARY METRICS =============== -| Metric | Value | -|------------------------+---------------------| -| Backtesting from | 2019-01-01 00:00:00 | -| Backtesting to | 2019-05-01 00:00:00 | -| Max open trades | 3 | -| | | -| Total/Daily Avg Trades | 429 / 3.575 | -| Starting balance | 0.01000000 BTC | -| Final balance | 0.01762792 BTC | -| Absolute profit | 0.00762792 BTC | -| Total profit % | 76.2% | -| CAGR % | 460.87% | -| Trades per day | 3.575 | -| Avg. stake amount | 0.001 BTC | -| Total trade volume | 0.429 BTC | -| | | -| Best Pair | LSK/BTC 26.26% | -| Worst Pair | ZEC/BTC -10.18% | -| Best Trade | LSK/BTC 4.25% | -| Worst Trade | ZEC/BTC -10.25% | -| Best day | 0.00076 BTC | -| Worst day | -0.00036 BTC | -| Days win/draw/lose | 12 / 82 / 25 | -| Avg. Duration Winners | 4:23:00 | -| Avg. Duration Loser | 6:55:00 | -| Rejected Entry signals | 3089 | -| Entry/Exit Timeouts | 0 / 0 | -| | | -| Min balance | 0.00945123 BTC | -| Max balance | 0.01846651 BTC | -| Drawdown (Account) | 13.33% | -| Drawdown | 0.0015 BTC | -| Drawdown high | 0.0013 BTC | -| Drawdown low | -0.0002 BTC | -| Drawdown Start | 2019-02-15 14:10:00 | -| Drawdown End | 2019-04-11 18:15:00 | -| Market change | -5.88% | -=============================================== +================== SUMMARY METRICS ================== +| Metric | Value | +|-----------------------------+---------------------| +| Backtesting from | 2019-01-01 00:00:00 | +| Backtesting to | 2019-05-01 00:00:00 | +| Max open trades | 3 | +| | | +| Total/Daily Avg Trades | 429 / 3.575 | +| Starting balance | 0.01000000 BTC | +| Final balance | 0.01762792 BTC | +| Absolute profit | 0.00762792 BTC | +| Total profit % | 76.2% | +| CAGR % | 460.87% | +| Avg. stake amount | 0.001 BTC | +| Total trade volume | 0.429 BTC | +| | | +| Long / Short | 352 / 77 | +| Total profit Long % | 1250.58% | +| Total profit Short % | -15.02% | +| Absolute profit Long | 0.00838792 BTC | +| Absolute profit Short | -0.00076 BTC | +| | | +| Best Pair | LSK/BTC 26.26% | +| Worst Pair | ZEC/BTC -10.18% | +| Best Trade | LSK/BTC 4.25% | +| Worst Trade | ZEC/BTC -10.25% | +| Best day | 0.00076 BTC | +| Worst day | -0.00036 BTC | +| Days win/draw/lose | 12 / 82 / 25 | +| Avg. Duration Winners | 4:23:00 | +| Avg. Duration Loser | 6:55:00 | +| Rejected Entry signals | 3089 | +| Entry/Exit Timeouts | 0 / 0 | +| | | +| Min balance | 0.00945123 BTC | +| Max balance | 0.01846651 BTC | +| Max % of account underwater | 25.19% | +| Absolute Drawdown (Account) | 13.33% | +| Drawdown | 0.0015 BTC | +| Drawdown high | 0.0013 BTC | +| Drawdown low | -0.0002 BTC | +| Drawdown Start | 2019-02-15 14:10:00 | +| Drawdown End | 2019-04-11 18:15:00 | +| Market change | -5.88% | +===================================================== ``` ### Backtesting report table @@ -380,43 +386,47 @@ It contains some useful key metrics about performance of your strategy on backte ================== SUMMARY METRICS ================== | Metric | Value | |-----------------------------+---------------------| -| Backtesting from | 2022-02-01 00:00:00 | -| Backtesting to | 2022-03-15 00:15:00 | -| Max open trades | 10 | +| Backtesting from | 2019-01-01 00:00:00 | +| Backtesting to | 2019-05-01 00:00:00 | +| Max open trades | 3 | | | | -| Total/Daily Avg Trades | 77 / 1.83 | -| Starting balance | 1000 USDT | -| Final balance | 1135.843 USDT | -| Absolute profit | 135.843 USDT | -| Total profit % | 13.58% | -| CAGR % | 202.51% | -| Trades per day | 1.83 | -| Avg. daily profit % | 0.32% | -| Avg. stake amount | 105.996 USDT | -| Total trade volume | 8161.695 USDT | +| Total/Daily Avg Trades | 429 / 3.575 | +| Starting balance | 0.01000000 BTC | +| Final balance | 0.01762792 BTC | +| Absolute profit | 0.00762792 BTC | +| Total profit % | 76.2% | +| CAGR % | 460.87% | +| Avg. stake amount | 0.001 BTC | +| Total trade volume | 0.429 BTC | | | | -| Best Pair | THETA/USDT 61.28% | -| Worst Pair | SAND/USDT -15.57% | -| Best trade | THETA/USDT 25.47% | -| Worst trade | SAND/USDT -5.19% | -| Best day | 73.347 USDT | -| Worst day | -56.261 USDT | -| Days win/draw/lose | 12 / 9 / 11 | -| Avg. Duration Winners | 1 day, 19:30:00 | -| Avg. Duration Loser | 20:31:00 | -| Rejected Entry signals | 16959 | +| Long / Short | 352 / 77 | +| Total profit Long % | 1250.58% | +| Total profit Short % | -15.02% | +| Absolute profit Long | 0.00838792 BTC | +| Absolute profit Short | -0.00076 BTC | +| | | +| Best Pair | LSK/BTC 26.26% | +| Worst Pair | ZEC/BTC -10.18% | +| Best Trade | LSK/BTC 4.25% | +| Worst Trade | ZEC/BTC -10.25% | +| Best day | 0.00076 BTC | +| Worst day | -0.00036 BTC | +| Days win/draw/lose | 12 / 82 / 25 | +| Avg. Duration Winners | 4:23:00 | +| Avg. Duration Loser | 6:55:00 | +| Rejected Entry signals | 3089 | | Entry/Exit Timeouts | 0 / 0 | | | | -| Min balance | 970.12 USDT | -| Max balance | 1141.775 USDT | -| Max % of account underwater | 7.07% | -| Absolute Drawdown (Account) | 7.07% | -| Absolute Drawdown | 77.666 USDT | -| Drawdown high | 97.995 USDT | -| Drawdown low | 20.329 USDT | -| Drawdown Start | 2022-02-11 08:00:00 | -| Drawdown End | 2022-02-13 15:30:00 | -| Market change | -6.67% | +| Min balance | 0.00945123 BTC | +| Max balance | 0.01846651 BTC | +| Max % of account underwater | 25.19% | +| Absolute Drawdown (Account) | 13.33% | +| Drawdown | 0.0015 BTC | +| Drawdown high | 0.0013 BTC | +| Drawdown low | -0.0002 BTC | +| Drawdown Start | 2019-02-15 14:10:00 | +| Drawdown End | 2019-04-11 18:15:00 | +| Market change | -5.88% | ===================================================== ``` From 8e1cdb9103789f425e93d57faf1b16b6f00e6d23 Mon Sep 17 00:00:00 2001 From: talentoscope <22162766+talentoscope@users.noreply.github.com> Date: Mon, 2 May 2022 23:20:13 +0100 Subject: [PATCH 254/254] Update setup.sh Added curl to dependencies for Debian systems --- setup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.sh b/setup.sh index e0b010387..dcf6c02c7 100755 --- a/setup.sh +++ b/setup.sh @@ -155,7 +155,7 @@ function install_macos() { # Install bot Debian_ubuntu function install_debian() { sudo apt-get update - sudo apt-get install -y gcc build-essential autoconf libtool pkg-config make wget git $(echo lib${PYTHON}-dev ${PYTHON}-venv) + sudo apt-get install -y gcc build-essential autoconf libtool pkg-config make wget git curl $(echo lib${PYTHON}-dev ${PYTHON}-venv) install_talib }