From 455954e0e3d79a82c58bd9ae4801a811c86d7a61 Mon Sep 17 00:00:00 2001 From: Robert Caulk Date: Sun, 1 Feb 2026 15:14:48 +0100 Subject: [PATCH 001/315] feat: Add tensorboard callback to lightgbm --- .../prediction_models/LightGBMClassifier.py | 6 +++++ .../LightGBMClassifierMultiTarget.py | 7 ++++++ .../prediction_models/LightGBMRegressor.py | 7 ++++++ .../LightGBMRegressorMultiTarget.py | 7 ++++++ freqtrade/freqai/tensorboard/__init__.py | 5 +++- .../freqai/tensorboard/lightgbm_callback.py | 24 +++++++++++++++++++ 6 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 freqtrade/freqai/tensorboard/lightgbm_callback.py diff --git a/freqtrade/freqai/prediction_models/LightGBMClassifier.py b/freqtrade/freqai/prediction_models/LightGBMClassifier.py index e17f7417c..75bbe2d1a 100644 --- a/freqtrade/freqai/prediction_models/LightGBMClassifier.py +++ b/freqtrade/freqai/prediction_models/LightGBMClassifier.py @@ -5,6 +5,7 @@ from lightgbm import LGBMClassifier from freqtrade.freqai.base_models.BaseClassifierModel import BaseClassifierModel from freqtrade.freqai.data_kitchen import FreqaiDataKitchen +from freqtrade.freqai.tensorboard import LightGBMCallback logger = logging.getLogger(__name__) @@ -46,6 +47,10 @@ class LightGBMClassifier(BaseClassifierModel): init_model = self.get_init_model(dk.pair) model = LGBMClassifier(**self.model_training_parameters) + activate_tensorboard = self.freqai_info.get("activate_tensorboard", True) + callbacks = [] + if LightGBMCallback is not None: + callbacks = [LightGBMCallback(dk.data_path, activate_tensorboard)] model.fit( X=X, y=y, @@ -53,6 +58,7 @@ class LightGBMClassifier(BaseClassifierModel): sample_weight=train_weights, eval_sample_weight=[test_weights], init_model=init_model, + callbacks=callbacks, ) return model diff --git a/freqtrade/freqai/prediction_models/LightGBMClassifierMultiTarget.py b/freqtrade/freqai/prediction_models/LightGBMClassifierMultiTarget.py index 9fb775614..4e4d981b8 100644 --- a/freqtrade/freqai/prediction_models/LightGBMClassifierMultiTarget.py +++ b/freqtrade/freqai/prediction_models/LightGBMClassifierMultiTarget.py @@ -6,6 +6,7 @@ from lightgbm import LGBMClassifier from freqtrade.freqai.base_models.BaseClassifierModel import BaseClassifierModel from freqtrade.freqai.base_models.FreqaiMultiOutputClassifier import FreqaiMultiOutputClassifier from freqtrade.freqai.data_kitchen import FreqaiDataKitchen +from freqtrade.freqai.tensorboard import LightGBMCallback logger = logging.getLogger(__name__) @@ -53,6 +54,11 @@ class LightGBMClassifierMultiTarget(BaseClassifierModel): else: init_models = [None] * y.shape[1] + activate_tensorboard = self.freqai_info.get("activate_tensorboard", True) + callbacks = [] + if LightGBMCallback is not None: + callbacks = [LightGBMCallback(dk.data_path, activate_tensorboard)] + fit_params = [] for i in range(len(eval_sets)): fit_params.append( @@ -60,6 +66,7 @@ class LightGBMClassifierMultiTarget(BaseClassifierModel): "eval_set": eval_sets[i], "eval_sample_weight": eval_weights, "init_model": init_models[i], + "callbacks": callbacks, } ) diff --git a/freqtrade/freqai/prediction_models/LightGBMRegressor.py b/freqtrade/freqai/prediction_models/LightGBMRegressor.py index d55cd0ca2..af89d4825 100644 --- a/freqtrade/freqai/prediction_models/LightGBMRegressor.py +++ b/freqtrade/freqai/prediction_models/LightGBMRegressor.py @@ -5,6 +5,7 @@ from lightgbm import LGBMRegressor from freqtrade.freqai.base_models.BaseRegressionModel import BaseRegressionModel from freqtrade.freqai.data_kitchen import FreqaiDataKitchen +from freqtrade.freqai.tensorboard import LightGBMCallback logger = logging.getLogger(__name__) @@ -42,6 +43,11 @@ class LightGBMRegressor(BaseRegressionModel): model = LGBMRegressor(**self.model_training_parameters) + activate_tensorboard = self.freqai_info.get("activate_tensorboard", True) + callbacks = [] + if LightGBMCallback is not None: + callbacks = [LightGBMCallback(dk.data_path, activate_tensorboard)] + model.fit( X=X, y=y, @@ -49,6 +55,7 @@ class LightGBMRegressor(BaseRegressionModel): sample_weight=train_weights, eval_sample_weight=[eval_weights], init_model=init_model, + callbacks=callbacks, ) return model diff --git a/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py b/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py index c4669a79d..8f374190b 100644 --- a/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py +++ b/freqtrade/freqai/prediction_models/LightGBMRegressorMultiTarget.py @@ -6,6 +6,7 @@ from lightgbm import LGBMRegressor from freqtrade.freqai.base_models.BaseRegressionModel import BaseRegressionModel from freqtrade.freqai.base_models.FreqaiMultiOutputRegressor import FreqaiMultiOutputRegressor from freqtrade.freqai.data_kitchen import FreqaiDataKitchen +from freqtrade.freqai.tensorboard import LightGBMCallback logger = logging.getLogger(__name__) @@ -55,6 +56,11 @@ class LightGBMRegressorMultiTarget(BaseRegressionModel): else: init_models = [None] * y.shape[1] + activate_tensorboard = self.freqai_info.get("activate_tensorboard", True) + callbacks = [] + if LightGBMCallback is not None: + callbacks = [LightGBMCallback(dk.data_path, activate_tensorboard)] + fit_params = [] for i in range(len(eval_sets)): fit_params.append( @@ -62,6 +68,7 @@ class LightGBMRegressorMultiTarget(BaseRegressionModel): "eval_set": eval_sets[i], "eval_sample_weight": eval_weights, "init_model": init_models[i], + "callbacks": callbacks, } ) diff --git a/freqtrade/freqai/tensorboard/__init__.py b/freqtrade/freqai/tensorboard/__init__.py index 183c25b22..68d045bf8 100644 --- a/freqtrade/freqai/tensorboard/__init__.py +++ b/freqtrade/freqai/tensorboard/__init__.py @@ -1,9 +1,11 @@ # ensure users can still use a non-torch freqai version try: + from freqtrade.freqai.tensorboard.lightgbm_callback import LightGBMTensorboardCallback from freqtrade.freqai.tensorboard.tensorboard import TensorBoardCallback, TensorboardLogger TBLogger = TensorboardLogger TBCallback = TensorBoardCallback + LightGBMCallback = LightGBMTensorboardCallback except ModuleNotFoundError: from freqtrade.freqai.tensorboard.base_tensorboard import ( BaseTensorBoardCallback, @@ -12,5 +14,6 @@ except ModuleNotFoundError: TBLogger = BaseTensorboardLogger # type: ignore TBCallback = BaseTensorBoardCallback # type: ignore + LightGBMCallback = None # type: ignore -__all__ = ("TBLogger", "TBCallback") +__all__ = ("TBLogger", "TBCallback", "LightGBMCallback") diff --git a/freqtrade/freqai/tensorboard/lightgbm_callback.py b/freqtrade/freqai/tensorboard/lightgbm_callback.py new file mode 100644 index 000000000..4e9bec804 --- /dev/null +++ b/freqtrade/freqai/tensorboard/lightgbm_callback.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from freqtrade.freqai.tensorboard import TBLogger + + +class LightGBMTensorboardCallback: + def __init__(self, logdir, activate: bool) -> None: + self.activate = activate + self.logger = TBLogger(logdir, activate) + + def __call__(self, env) -> None: + if not self.activate: + return + + evals = getattr(env, "evaluation_result_list", None) + if not evals: + return + + for data_name, metric_name, value, _ in evals: + self.logger.log_scalar(f"{data_name}-{metric_name}", value, env.iteration) + + end_iteration = getattr(env, "end_iteration", None) + if end_iteration is not None and env.iteration + 1 >= end_iteration: + self.logger.close() From 783c365c10c1976cd22ea44747e15c45369df6aa Mon Sep 17 00:00:00 2001 From: Robert Caulk Date: Thu, 5 Feb 2026 17:15:00 +0100 Subject: [PATCH 002/315] fix: Avoid circular import --- freqtrade/freqai/tensorboard/lightgbm_callback.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqai/tensorboard/lightgbm_callback.py b/freqtrade/freqai/tensorboard/lightgbm_callback.py index 4e9bec804..c71a43ec5 100644 --- a/freqtrade/freqai/tensorboard/lightgbm_callback.py +++ b/freqtrade/freqai/tensorboard/lightgbm_callback.py @@ -1,12 +1,12 @@ from __future__ import annotations -from freqtrade.freqai.tensorboard import TBLogger +from freqtrade.freqai.tensorboard.tensorboard import TensorboardLogger class LightGBMTensorboardCallback: def __init__(self, logdir, activate: bool) -> None: self.activate = activate - self.logger = TBLogger(logdir, activate) + self.logger = TensorboardLogger(logdir, activate) def __call__(self, env) -> None: if not self.activate: From 6814bc84aadb79419e37f6c3e83a5ad626c556ae Mon Sep 17 00:00:00 2001 From: Robert Caulk Date: Thu, 5 Feb 2026 17:24:17 +0100 Subject: [PATCH 003/315] fix: Try to fix linting --- freqtrade/freqai/prediction_models/LightGBMClassifier.py | 3 ++- freqtrade/freqai/prediction_models/LightGBMRegressor.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqai/prediction_models/LightGBMClassifier.py b/freqtrade/freqai/prediction_models/LightGBMClassifier.py index 75bbe2d1a..0b78c4129 100644 --- a/freqtrade/freqai/prediction_models/LightGBMClassifier.py +++ b/freqtrade/freqai/prediction_models/LightGBMClassifier.py @@ -1,4 +1,5 @@ import logging +from collections.abc import Callable from typing import Any from lightgbm import LGBMClassifier @@ -48,7 +49,7 @@ class LightGBMClassifier(BaseClassifierModel): model = LGBMClassifier(**self.model_training_parameters) activate_tensorboard = self.freqai_info.get("activate_tensorboard", True) - callbacks = [] + callbacks: list[Callable[..., Any]] = [] if LightGBMCallback is not None: callbacks = [LightGBMCallback(dk.data_path, activate_tensorboard)] model.fit( diff --git a/freqtrade/freqai/prediction_models/LightGBMRegressor.py b/freqtrade/freqai/prediction_models/LightGBMRegressor.py index af89d4825..abd838eee 100644 --- a/freqtrade/freqai/prediction_models/LightGBMRegressor.py +++ b/freqtrade/freqai/prediction_models/LightGBMRegressor.py @@ -1,4 +1,5 @@ import logging +from collections.abc import Callable from typing import Any from lightgbm import LGBMRegressor @@ -44,7 +45,7 @@ class LightGBMRegressor(BaseRegressionModel): model = LGBMRegressor(**self.model_training_parameters) activate_tensorboard = self.freqai_info.get("activate_tensorboard", True) - callbacks = [] + callbacks: list[Callable[..., Any]] = [] if LightGBMCallback is not None: callbacks = [LightGBMCallback(dk.data_path, activate_tensorboard)] From c29956866d43ee6b6930f82fe408a7842ff2f5be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 03:02:43 +0000 Subject: [PATCH 004/315] chore(deps-dev): bump types-cachetools in the types group Bumps the types group with 1 update: [types-cachetools](https://github.com/python/typeshed). Updates `types-cachetools` from 6.2.0.20251022 to 6.2.0.20260317 - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-cachetools dependency-version: 6.2.0.20260317 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types ... 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 850cf4578..df34b5426 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -24,7 +24,7 @@ nbconvert==7.17.0 # mypy types scipy-stubs==1.17.1.2 # keep in sync with `scipy` in `requirements-hyperopt.txt` -types-cachetools==6.2.0.20251022 +types-cachetools==6.2.0.20260317 types-filelock==3.2.7 types-requests==2.32.4.20260107 types-tabulate==0.10.0.20260308 From 6541450c7c9cbcfd65bd25f08472f880b3d7ffe6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 03:03:04 +0000 Subject: [PATCH 005/315] chore(deps-dev): bump pytest-cov from 7.0.0 to 7.1.0 in the pytest group Bumps the pytest group with 1 update: [pytest-cov](https://github.com/pytest-dev/pytest-cov). Updates `pytest-cov` from 7.0.0 to 7.1.0 - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v7.0.0...v7.1.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-version: 7.1.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: pytest ... 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 850cf4578..0962b4cc6 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -11,7 +11,7 @@ mypy==1.19.1 pre-commit==4.5.1 pytest==9.0.2 pytest-asyncio==1.3.0 -pytest-cov==7.0.0 +pytest-cov==7.1.0 pytest-mock==3.15.1 pytest-random-order==1.2.0 pytest-timeout==2.4.0 From 8d024f5e082d72fcac856d3e29ee183fdfcf0b82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 03:03:15 +0000 Subject: [PATCH 006/315] chore(deps): bump mkdocs-material in the mkdocs group Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material). Updates `mkdocs-material` from 9.7.5 to 9.7.6 - [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/9.7.5...9.7.6) --- updated-dependencies: - dependency-name: mkdocs-material dependency-version: 9.7.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: mkdocs ... 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 5267c55df..beb93d144 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.10.2 mkdocs==1.6.1 -mkdocs-material==9.7.5 +mkdocs-material==9.7.6 mdx_truly_sane_lists==1.3 pymdown-extensions==10.21 jinja2==3.1.6 From e8fde061b53b7ff205f41a33236360ef0734daca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 03:03:39 +0000 Subject: [PATCH 007/315] chore(deps): bump optuna from 4.7.0 to 4.8.0 Bumps [optuna](https://github.com/optuna/optuna) from 4.7.0 to 4.8.0. - [Release notes](https://github.com/optuna/optuna/releases) - [Commits](https://github.com/optuna/optuna/compare/v4.7.0...v4.8.0) --- updated-dependencies: - dependency-name: optuna dependency-version: 4.8.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index a632bd3b2..b1f2abd3e 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -5,5 +5,5 @@ scipy==1.17.1 scikit-learn==1.8.0 filelock==3.25.2 -optuna==4.7.0 +optuna==4.8.0 cmaes==0.12.0 From 9df068c4267d102cc1656f16d7875bec5a5eeafe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 03:04:11 +0000 Subject: [PATCH 008/315] chore(deps): bump uvicorn from 0.41.0 to 0.42.0 Bumps [uvicorn](https://github.com/Kludex/uvicorn) from 0.41.0 to 0.42.0. - [Release notes](https://github.com/Kludex/uvicorn/releases) - [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md) - [Commits](https://github.com/Kludex/uvicorn/compare/0.41.0...0.42.0) --- updated-dependencies: - dependency-name: uvicorn dependency-version: 0.42.0 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 9998fd3ff..0f4bef5e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,7 +39,7 @@ sdnotify==0.3.2 # API Server fastapi==0.135.1 pydantic==2.12.5 -uvicorn==0.41.0 +uvicorn==0.42.0 pyjwt==2.12.1 aiofiles==25.1.0 psutil==7.2.2 From 1615c0bd92c83d7a02ea91abbf3309bceceda7b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 03:04:24 +0000 Subject: [PATCH 009/315] chore(deps): bump python-telegram-bot from 22.6 to 22.7 Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 22.6 to 22.7. - [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases) - [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v22.6...v22.7) --- updated-dependencies: - dependency-name: python-telegram-bot dependency-version: '22.7' 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 9998fd3ff..c487197a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ ccxt==4.5.44 cryptography==46.0.6 aiohttp==3.13.3 SQLAlchemy==2.0.48 -python-telegram-bot==22.6 +python-telegram-bot==22.7 # can't be hard-pinned due to telegram-bot pinning httpx with ~ httpx>=0.24.1 humanize==4.15.0 From 9a94d97e697dc3ee6fd5aab70759e38adf671718 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 30 Mar 2026 06:47:13 +0200 Subject: [PATCH 010/315] chore: bump types-cachetools in pre-commit config --- .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 5f5490799..35a973ff7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: - id: mypy exclude: build_helpers additional_dependencies: - - types-cachetools==6.2.0.20251022 + - types-cachetools==6.2.0.20260317 - types-filelock==3.2.7 - types-requests==2.32.4.20260107 - types-tabulate==0.10.0.20260308 From 5c129506ae4428d685ea989a6a557ee3f5b13b56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 04:48:19 +0000 Subject: [PATCH 011/315] chore(deps): bump ccxt from 4.5.44 to 4.5.45 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.5.44 to 4.5.45. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Commits](https://github.com/ccxt/ccxt/compare/v4.5.44...v4.5.45) --- updated-dependencies: - dependency-name: ccxt dependency-version: 4.5.45 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 71c79ef9a..1f6d07a6a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ ft-pandas-ta==0.3.16 ta-lib==0.6.8 technical==1.5.4 -ccxt==4.5.44 +ccxt==4.5.45 cryptography==46.0.6 aiohttp==3.13.3 SQLAlchemy==2.0.48 From 326ef6d49be41302f8d1c8038ee107f57cef737b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 30 Mar 2026 06:51:31 +0200 Subject: [PATCH 012/315] test: update krakenfutures "filled" test --- tests/exchange_online/conftest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/exchange_online/conftest.py b/tests/exchange_online/conftest.py index ba04598cd..50d2fe116 100644 --- a/tests/exchange_online/conftest.py +++ b/tests/exchange_online/conftest.py @@ -603,6 +603,7 @@ EXCHANGES: dict[str, TestExchangeOnlineSetup] = { "status": "closed", "type": "market", "amount": 0.0004, + "filled": 0.0004, "side": "sell", "triggerPrice": None, "stopPrice": None, @@ -646,6 +647,8 @@ EXCHANGES: dict[str, TestExchangeOnlineSetup] = { "price": None, "status": "open", "amount": 0.0004, + # TODO: filled should be 0, not None. + "filled": None, "side": "buy", "triggerPrice": 71641.0, "stopPrice": 71641.0, @@ -681,8 +684,7 @@ EXCHANGES: dict[str, TestExchangeOnlineSetup] = { "datetime": "2026-03-21T07:32:21.555Z", "price": None, "status": "canceled", - # TODO: filled should be 0, not None. - "filled": None, + "filled": 0.0, }, }, ], From 230281f8f2137ba80c31b7615a6a6cf13c00be7f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 30 Mar 2026 07:02:22 +0200 Subject: [PATCH 013/315] chore: bump develop version to 2026.4-dev --- freqtrade/__init__.py | 2 +- ft_client/freqtrade_client/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index a9c6dec18..7238eab9d 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,6 +1,6 @@ """Freqtrade bot""" -__version__ = "2026.3-dev" +__version__ = "2026.4-dev" if "dev" in __version__: from pathlib import Path diff --git a/ft_client/freqtrade_client/__init__.py b/ft_client/freqtrade_client/__init__.py index 258687a7a..ba368c8e4 100644 --- a/ft_client/freqtrade_client/__init__.py +++ b/ft_client/freqtrade_client/__init__.py @@ -1,7 +1,7 @@ from freqtrade_client.ft_rest_client import FtRestClient -__version__ = "2026.3-dev" +__version__ = "2026.4-dev" if "dev" in __version__: from pathlib import Path From a9f6fcc7bb7ba0b014cf342525f4562d90f1dc17 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 30 Mar 2026 07:06:07 +0200 Subject: [PATCH 014/315] chore: uv.tool add exclude-newer --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 3a2d88b17..c60009ef2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -217,6 +217,9 @@ reportRedeclaration = false # 1 reportReturnType = false # 28 reportTypedDictNotRequiredAccess = false # 27 +[tool.uv] +exclude-newer = "1 week" +exclude-newer-package = { ccxt = false } [tool.ruff] line-length = 100 From e703d6fea9ec6e2006e8990c106365f10725ece4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 30 Mar 2026 07:13:10 +0200 Subject: [PATCH 015/315] chore: temporarily revert exclude-newer --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c60009ef2..fb7639d7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -217,9 +217,9 @@ reportRedeclaration = false # 1 reportReturnType = false # 28 reportTypedDictNotRequiredAccess = false # 27 -[tool.uv] -exclude-newer = "1 week" -exclude-newer-package = { ccxt = false } +# [tool.uv] +# exclude-newer = "1 week" +# exclude-newer-package = { ccxt = false } [tool.ruff] line-length = 100 From 6a8d1fff8f2804839c49ca0732e5c368fa6b998c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 05:15:20 +0000 Subject: [PATCH 016/315] chore(deps-dev): bump ruff from 0.15.6 to 0.15.7 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.6 to 0.15.7. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.6...0.15.7) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.7 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 0962b4cc6..1a634c947 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ -r requirements-freqai-rl.txt -r docs/requirements-docs.txt -ruff==0.15.6 +ruff==0.15.7 mypy==1.19.1 pre-commit==4.5.1 pytest==9.0.2 From 5782e39fc5adad203eabdd0d1f3c8e33e1c2150c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Mar 2026 05:17:09 +0000 Subject: [PATCH 017/315] chore(deps-dev): bump scipy-stubs in the scipy group Bumps the scipy group with 1 update: [scipy-stubs](https://github.com/scipy/scipy-stubs). Updates `scipy-stubs` from 1.17.1.2 to 1.17.1.3 - [Release notes](https://github.com/scipy/scipy-stubs/releases) - [Commits](https://github.com/scipy/scipy-stubs/compare/v1.17.1.2...v1.17.1.3) --- updated-dependencies: - dependency-name: scipy-stubs dependency-version: 1.17.1.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: scipy ... 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 4ecbbf26f..31b8435da 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -23,7 +23,7 @@ time-machine==3.2.0 nbconvert==7.17.0 # mypy types -scipy-stubs==1.17.1.2 # keep in sync with `scipy` in `requirements-hyperopt.txt` +scipy-stubs==1.17.1.3 # keep in sync with `scipy` in `requirements-hyperopt.txt` types-cachetools==6.2.0.20260317 types-filelock==3.2.7 types-requests==2.32.4.20260107 From 4b9ab0a54fdd640ca3027997af669d9602bc60da Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 30 Mar 2026 07:18:37 +0200 Subject: [PATCH 018/315] chore: bump scipy-stubs in pre-commit config --- .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 35a973ff7..9736e606e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: - types-requests==2.32.4.20260107 - types-tabulate==0.10.0.20260308 - types-python-dateutil==2.9.0.20260305 - - scipy-stubs==1.17.1.2 + - scipy-stubs==1.17.1.3 - SQLAlchemy==2.0.48 # stages: [push] From 61e27462b179bfe9261206c869804c5a873705ca Mon Sep 17 00:00:00 2001 From: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com> Date: Tue, 31 Mar 2026 03:57:08 +0000 Subject: [PATCH 019/315] chore: update pre-commit hooks --- .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 9736e606e..387b4cbfe 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.15.7' + rev: 'v0.15.8' hooks: - id: ruff - id: ruff-format From ea51c646cddee46043796d3e478e172037b4eb1c Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Mar 2026 06:40:03 +0200 Subject: [PATCH 020/315] chore: add uv exclude-newer safeguard --- pyproject.toml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fb7639d7f..eaab3445e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -217,9 +217,14 @@ reportRedeclaration = false # 1 reportReturnType = false # 28 reportTypedDictNotRequiredAccess = false # 27 -# [tool.uv] -# exclude-newer = "1 week" -# exclude-newer-package = { ccxt = false } +[tool.uv] +exclude-newer = "1 week" + +[tool.uv.exclude-newer-package] +ccxt = false +cryptography = "3 days" +requests = "5 days" +build= "5 days" [tool.ruff] line-length = 100 From c21b8587a6564f92f49dea67ae7207a1cac53d78 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Mar 2026 06:52:52 +0200 Subject: [PATCH 021/315] fix: incorrect balance estimation while entry order is not filled closes #12993 --- freqtrade/rpc/rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 23a78fad8..37b8dfa6d 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -875,7 +875,7 @@ class RPC: for symbol, pos in self._freqtrade.wallets.get_all_positions().items(): est_stake = pos.collateral pos_base = self._freqtrade.exchange.get_pair_base_currency(symbol) - if pos.leverage: + if pos.leverage and pos.position: try: rate = self._freqtrade.exchange.get_conversion_rate(pos_base, stake_currency) if rate: From 7e7e475d4a79d7536341929a6b96161c395743ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Mar 2026 07:19:37 +0200 Subject: [PATCH 022/315] chore: update deploy-docs to use uv --- .github/workflows/deploy-docs.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index 8b9d114b6..edd708274 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -29,12 +29,17 @@ jobs: - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: - python-version: '3.12' + python-version: '3.13' + + - name: Install uv + uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1 + with: + activate-environment: true + python-version: '3.13' - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install -r docs/requirements-docs.txt + uv pip install -r docs/requirements-docs.txt - name: Fetch gh-pages branch run: | From b0c14d1122972a0ffd494b9d4a771b1097258580 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Mar 2026 07:21:48 +0200 Subject: [PATCH 023/315] docs: update non-working links to supported-futures --- docs/faq.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index aea0030ad..55af35d42 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -2,7 +2,7 @@ ## Supported Markets -Freqtrade supports spot trading, as well as futures trading for some selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an up-to-date list of supported exchanges. +Freqtrade supports spot trading, as well as futures trading for some selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges) for an up-to-date list of supported exchanges. ### Can my bot open short positions? @@ -14,7 +14,7 @@ In spot markets, you can in some cases use leveraged spot tokens, which reflect ### Can my bot trade options or futures? -Futures trading is supported for selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an up-to-date list of supported exchanges. +Futures trading is supported for selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges) for an up-to-date list of supported exchanges. ## Beginner Tips & Tricks From 8eb2edc0e247561ab824f8f4ab9a3dbf869c6365 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Apr 2026 07:24:33 +0200 Subject: [PATCH 024/315] feat: support 3.14 in setup scripts --- setup.ps1 | 4 ++-- setup.sh | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.ps1 b/setup.ps1 index cf57915fa..4fea27257 100644 --- a/setup.ps1 +++ b/setup.ps1 @@ -9,8 +9,8 @@ $VenvName = ".venv" $VenvDir = Join-Path $PSScriptRoot $VenvName # Supported Python minor versions (detection order: prefer newest first) -$SupportedMinorVersions = @(13,12,11) -# Build a human-readable supported versions string like "3.11, 3.12 and 3.13" +$SupportedMinorVersions = @(14,13,12,11) +# Build a human-readable supported versions string like "3.11, 3.12 3.13 and 3.14" $asc = $SupportedMinorVersions | Sort-Object if ($asc.Count -eq 1) { $SupportedPythonVersions = "3.$($asc[0])" diff --git a/setup.sh b/setup.sh index f4b9e0946..ad4d7455a 100755 --- a/setup.sh +++ b/setup.sh @@ -8,8 +8,8 @@ function echo_block() { } UV=false # Supported Python minor versions (order matters for detection) -SUPPORTED_MINOR_VERS=(13 12 11) -SUPPORTED_PY_VERSIONS="3.11, 3.12 and 3.13" +SUPPORTED_MINOR_VERS=(14 13 12 11) +SUPPORTED_PY_VERSIONS="3.11, 3.12, 3.13 and 3.14" function check_installed_pip() { ${PYTHON} -m pip > /dev/null @@ -254,7 +254,7 @@ function install() { install_redhat else echo "This script does not support your OS." - echo "If you have Python version 3.11 - 3.13, pip, virtualenv installed you can continue." + echo "If you have Python version 3.11 - 3.14, pip, virtualenv installed you can continue." echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell." sleep 10 fi From d1b378ead6c2842d518a10b2398ee5c4c35d76b6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Apr 2026 20:29:23 +0200 Subject: [PATCH 025/315] chore: bump armhf image to 3.11.15 --- docker/Dockerfile.armhf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile.armhf b/docker/Dockerfile.armhf index d654400eb..920bf82e0 100644 --- a/docker/Dockerfile.armhf +++ b/docker/Dockerfile.armhf @@ -1,4 +1,4 @@ -FROM python:3.11.14-slim-bookworm AS base +FROM python:3.11.15-slim-bookworm AS base # Setup env ENV LANG=C.UTF-8 From 384e0c5e5a1e69d0c0d8d9b4a2c2971e44ed5735 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Apr 2026 20:44:30 +0200 Subject: [PATCH 026/315] feat: update dockerfile base to python 3.14.3 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ade667989..7c09e18a6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.13.12-slim-trixie AS base +FROM python:3.14.3-slim-trixie AS base # Setup env ENV LANG=C.UTF-8 From cdffee23e17eed2ea31930d844639c35688c92e0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Apr 2026 22:20:34 +0200 Subject: [PATCH 027/315] docs: Add stoploss order type explainer closes #12984 --- docs/stoploss.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/stoploss.md b/docs/stoploss.md index e42a885c8..9a0bbb9e8 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -39,6 +39,22 @@ The Order-type will be ignored if only one mode is available. In that case, the bot will fallback to using the `emergency_exit` order type to place a market order as placing the stoploss order failed. Freqtrade currently does not implement a limitation to avoid this situation, so please ensure your stoploss values are within reasonable limits for your exchange or disable stoploss on exchange. +### Which order type is used for stoploss on exchange? + +The order type used for stoploss on exchange is determined by the `stoploss` value and the exchange capabilities. +If your selected exchange supports both stop-limit and stop-market orders, then the `stoploss` value will determine which order type is used for stoploss on exchange. +If your exchange only supports one of the two order types, you must configure your `stoploss` value accordingly, otherwise the bot will fail to start. + +### Which order type should i use for stoploss on exchange? + +If we translate the two stoploss order types into human words - they would be something like this: + +* **stoploss-market** -> "when stop triggers, get me the hell out of here at whatever price". +* **stoploss-limit** -> "when stop triggers, place a limit order x% below the stoploss price. I accept a loss of "stoploss + 1%" at worst - but if price jumps further - i accept to wait for price to get back down to me, potentially resulting in a much bigger loss than "stoploss + 1%". + +As a consequence, we recommend using stoploss-market orders whenever possible, as the main point of a stoploss is to get you out of a position when the market is crashing, and in such situations, you'll want to exit the position immediately at the best available price, rather than risking a limit order not getting filled and potentially incurring even greater losses. +The choice is ultimately up to you, but please be aware of the risk of using stoploss-limit orders, especially in volatile markets. + ### stoploss_on_exchange and stoploss_on_exchange_limit_ratio Enable or Disable stop loss on exchange. From 57b42b8cf091786e3d91d8b9b15bc5115e50335f Mon Sep 17 00:00:00 2001 From: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com> Date: Thu, 2 Apr 2026 04:15:15 +0000 Subject: [PATCH 028/315] chore: update binance leverage tiers --- .../exchange/binance_leverage_tiers.json | 1494 +++++++++++++++-- 1 file changed, 1384 insertions(+), 110 deletions(-) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index da0d66a97..6ff17bdf9 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -15152,6 +15152,161 @@ } } ], + "BASED/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "BASED/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": 1, + "initialLeverage": 50, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.015, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "BASED/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": 2, + "initialLeverage": 25, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.02, + "cum": 25.0 + } + }, + { + "tier": 3.0, + "symbol": "BASED/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": 3, + "initialLeverage": 20, + "notionalCap": 25000, + "notionalFloor": 10000, + "maintMarginRatio": 0.025, + "cum": 75.0 + } + }, + { + "tier": 4.0, + "symbol": "BASED/USDT:USDT", + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 62500.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 4, + "initialLeverage": 10, + "notionalCap": 62500, + "notionalFloor": 25000, + "maintMarginRatio": 0.05, + "cum": 700.0 + } + }, + { + "tier": 5.0, + "symbol": "BASED/USDT:USDT", + "currency": "USDT", + "minNotional": 62500.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 5, + "initialLeverage": 5, + "notionalCap": 125000, + "notionalFloor": 62500, + "maintMarginRatio": 0.1, + "cum": 3825.0 + } + }, + { + "tier": 6.0, + "symbol": "BASED/USDT:USDT", + "currency": "USDT", + "minNotional": 125000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 6, + "initialLeverage": 4, + "notionalCap": 250000, + "notionalFloor": 125000, + "maintMarginRatio": 0.125, + "cum": 6950.0 + } + }, + { + "tier": 7.0, + "symbol": "BASED/USDT:USDT", + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 7, + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 17375.0 + } + }, + { + "tier": 8.0, + "symbol": "BASED/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 8, + "initialLeverage": 2, + "notionalCap": 7500000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 59025.0 + } + }, + { + "tier": 9.0, + "symbol": "BASED/USDT:USDT", + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 12500000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 9, + "initialLeverage": 1, + "notionalCap": 12500000, + "notionalFloor": 7500000, + "maintMarginRatio": 0.5, + "cum": 1934025.0 + } + } + ], "BAT/USDT:USDT": [ { "tier": 1.0, @@ -21012,6 +21167,144 @@ } } ], + "BTC/USDT:USDT-260925": [ + { + "tier": 1.0, + "symbol": "BTC/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, + "info": { + "bracket": 1, + "initialLeverage": 50, + "notionalCap": 50000, + "notionalFloor": 0, + "maintMarginRatio": 0.01, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "BTC/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 375000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": 2, + "initialLeverage": 25, + "notionalCap": 375000, + "notionalFloor": 50000, + "maintMarginRatio": 0.02, + "cum": 500.0 + } + }, + { + "tier": 3.0, + "symbol": "BTC/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 375000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 3, + "initialLeverage": 10, + "notionalCap": 2000000, + "notionalFloor": 375000, + "maintMarginRatio": 0.05, + "cum": 11750.0 + } + }, + { + "tier": 4.0, + "symbol": "BTC/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 4, + "initialLeverage": 5, + "notionalCap": 4000000, + "notionalFloor": 2000000, + "maintMarginRatio": 0.1, + "cum": 111750.0 + } + }, + { + "tier": 5.0, + "symbol": "BTC/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 4000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 5, + "initialLeverage": 4, + "notionalCap": 10000000, + "notionalFloor": 4000000, + "maintMarginRatio": 0.125, + "cum": 211750.0 + } + }, + { + "tier": 6.0, + "symbol": "BTC/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 10000000.0, + "maxNotional": 20000000.0, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3.0, + "info": { + "bracket": 6, + "initialLeverage": 3, + "notionalCap": 20000000, + "notionalFloor": 10000000, + "maintMarginRatio": 0.15, + "cum": 461750.0 + } + }, + { + "tier": 7.0, + "symbol": "BTC/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 20000000.0, + "maxNotional": 40000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 7, + "initialLeverage": 2, + "notionalCap": 40000000, + "notionalFloor": 20000000, + "maintMarginRatio": 0.25, + "cum": 2461750.0 + } + }, + { + "tier": 8.0, + "symbol": "BTC/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 40000000.0, + "maxNotional": 120000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 8, + "initialLeverage": 1, + "notionalCap": 120000000, + "notionalFloor": 40000000, + "maintMarginRatio": 0.5, + "cum": 12461750.0 + } + } + ], "BTCDOM/USDT:USDT": [ { "tier": 1.0, @@ -21428,6 +21721,178 @@ } } ], + "BZ/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "BZ/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 100.0, + "info": { + "bracket": 1, + "initialLeverage": 100, + "notionalCap": 50000, + "notionalFloor": 0, + "maintMarginRatio": 0.005, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "BZ/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75.0, + "info": { + "bracket": 2, + "initialLeverage": 75, + "notionalCap": 400000, + "notionalFloor": 50000, + "maintMarginRatio": 0.0065, + "cum": 75.0 + } + }, + { + "tier": 3.0, + "symbol": "BZ/USDT:USDT", + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, + "info": { + "bracket": 3, + "initialLeverage": 50, + "notionalCap": 1000000, + "notionalFloor": 400000, + "maintMarginRatio": 0.01, + "cum": 1475.0 + } + }, + { + "tier": 4.0, + "symbol": "BZ/USDT:USDT", + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": 4, + "initialLeverage": 25, + "notionalCap": 4000000, + "notionalFloor": 1000000, + "maintMarginRatio": 0.02, + "cum": 11475.0 + } + }, + { + "tier": 5.0, + "symbol": "BZ/USDT:USDT", + "currency": "USDT", + "minNotional": 4000000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": 5, + "initialLeverage": 20, + "notionalCap": 8000000, + "notionalFloor": 4000000, + "maintMarginRatio": 0.025, + "cum": 31475.0 + } + }, + { + "tier": 6.0, + "symbol": "BZ/USDT:USDT", + "currency": "USDT", + "minNotional": 8000000.0, + "maxNotional": 40000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 6, + "initialLeverage": 10, + "notionalCap": 40000000, + "notionalFloor": 8000000, + "maintMarginRatio": 0.05, + "cum": 231475.0 + } + }, + { + "tier": 7.0, + "symbol": "BZ/USDT:USDT", + "currency": "USDT", + "minNotional": 40000000.0, + "maxNotional": 80000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 7, + "initialLeverage": 5, + "notionalCap": 80000000, + "notionalFloor": 40000000, + "maintMarginRatio": 0.1, + "cum": 2231475.0 + } + }, + { + "tier": 8.0, + "symbol": "BZ/USDT:USDT", + "currency": "USDT", + "minNotional": 80000000.0, + "maxNotional": 100000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 8, + "initialLeverage": 4, + "notionalCap": 100000000, + "notionalFloor": 80000000, + "maintMarginRatio": 0.125, + "cum": 4231475.0 + } + }, + { + "tier": 9.0, + "symbol": "BZ/USDT:USDT", + "currency": "USDT", + "minNotional": 100000000.0, + "maxNotional": 200000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 9, + "initialLeverage": 2, + "notionalCap": 200000000, + "notionalFloor": 100000000, + "maintMarginRatio": 0.25, + "cum": 16731475.0 + } + }, + { + "tier": 10.0, + "symbol": "BZ/USDT:USDT", + "currency": "USDT", + "minNotional": 200000000.0, + "maxNotional": 400000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 10, + "initialLeverage": 1, + "notionalCap": 400000000, + "notionalFloor": 200000000, + "maintMarginRatio": 0.5, + "cum": 66731475.0 + } + } + ], "C/USDT:USDT": [ { "tier": 1.0, @@ -24012,6 +24477,178 @@ } } ], + "CL/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "CL/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 100.0, + "info": { + "bracket": 1, + "initialLeverage": 100, + "notionalCap": 50000, + "notionalFloor": 0, + "maintMarginRatio": 0.005, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "CL/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75.0, + "info": { + "bracket": 2, + "initialLeverage": 75, + "notionalCap": 400000, + "notionalFloor": 50000, + "maintMarginRatio": 0.0065, + "cum": 75.0 + } + }, + { + "tier": 3.0, + "symbol": "CL/USDT:USDT", + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, + "info": { + "bracket": 3, + "initialLeverage": 50, + "notionalCap": 1000000, + "notionalFloor": 400000, + "maintMarginRatio": 0.01, + "cum": 1475.0 + } + }, + { + "tier": 4.0, + "symbol": "CL/USDT:USDT", + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": 4, + "initialLeverage": 25, + "notionalCap": 4000000, + "notionalFloor": 1000000, + "maintMarginRatio": 0.02, + "cum": 11475.0 + } + }, + { + "tier": 5.0, + "symbol": "CL/USDT:USDT", + "currency": "USDT", + "minNotional": 4000000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": 5, + "initialLeverage": 20, + "notionalCap": 8000000, + "notionalFloor": 4000000, + "maintMarginRatio": 0.025, + "cum": 31475.0 + } + }, + { + "tier": 6.0, + "symbol": "CL/USDT:USDT", + "currency": "USDT", + "minNotional": 8000000.0, + "maxNotional": 40000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 6, + "initialLeverage": 10, + "notionalCap": 40000000, + "notionalFloor": 8000000, + "maintMarginRatio": 0.05, + "cum": 231475.0 + } + }, + { + "tier": 7.0, + "symbol": "CL/USDT:USDT", + "currency": "USDT", + "minNotional": 40000000.0, + "maxNotional": 80000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 7, + "initialLeverage": 5, + "notionalCap": 80000000, + "notionalFloor": 40000000, + "maintMarginRatio": 0.1, + "cum": 2231475.0 + } + }, + { + "tier": 8.0, + "symbol": "CL/USDT:USDT", + "currency": "USDT", + "minNotional": 80000000.0, + "maxNotional": 100000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 8, + "initialLeverage": 4, + "notionalCap": 100000000, + "notionalFloor": 80000000, + "maintMarginRatio": 0.125, + "cum": 4231475.0 + } + }, + { + "tier": 9.0, + "symbol": "CL/USDT:USDT", + "currency": "USDT", + "minNotional": 100000000.0, + "maxNotional": 200000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 9, + "initialLeverage": 2, + "notionalCap": 200000000, + "notionalFloor": 100000000, + "maintMarginRatio": 0.25, + "cum": 16731475.0 + } + }, + { + "tier": 10.0, + "symbol": "CL/USDT:USDT", + "currency": "USDT", + "minNotional": 200000000.0, + "maxNotional": 400000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 10, + "initialLeverage": 1, + "notionalCap": 400000000, + "notionalFloor": 200000000, + "maintMarginRatio": 0.5, + "cum": 66731475.0 + } + } + ], "CLANKER/USDT:USDT": [ { "tier": 1.0, @@ -30160,15 +30797,15 @@ "symbol": "DRIFT/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 50, - "notionalCap": 5000, + "initialLeverage": 20, + "notionalCap": 10000, "notionalFloor": 0, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.025, "cum": 0.0 } }, @@ -30176,38 +30813,21 @@ "tier": 2.0, "symbol": "DRIFT/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, - "info": { - "bracket": 2, - "initialLeverage": 20, - "notionalCap": 10000, - "notionalFloor": 5000, - "maintMarginRatio": 0.025, - "cum": 50.0 - } - }, - { - "tier": 3.0, - "symbol": "DRIFT/USDT:USDT", - "currency": "USDT", "minNotional": 10000.0, "maxNotional": 20000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": 3, + "bracket": 2, "initialLeverage": 10, "notionalCap": 20000, "notionalFloor": 10000, "maintMarginRatio": 0.05, - "cum": 300.0 + "cum": 250.0 } }, { - "tier": 4.0, + "tier": 3.0, "symbol": "DRIFT/USDT:USDT", "currency": "USDT", "minNotional": 20000.0, @@ -30215,16 +30835,16 @@ "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": 4, + "bracket": 3, "initialLeverage": 5, "notionalCap": 50000, "notionalFloor": 20000, "maintMarginRatio": 0.1, - "cum": 1300.0 + "cum": 1250.0 } }, { - "tier": 5.0, + "tier": 4.0, "symbol": "DRIFT/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, @@ -30232,16 +30852,16 @@ "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 5, + "bracket": 4, "initialLeverage": 4, "notionalCap": 250000, "notionalFloor": 50000, "maintMarginRatio": 0.125, - "cum": 2550.0 + "cum": 2500.0 } }, { - "tier": 6.0, + "tier": 5.0, "symbol": "DRIFT/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, @@ -30249,46 +30869,46 @@ "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 6, + "bracket": 5, "initialLeverage": 3, "notionalCap": 500000, "notionalFloor": 250000, "maintMarginRatio": 0.1667, - "cum": 12975.0 + "cum": 12925.0 + } + }, + { + "tier": 6.0, + "symbol": "DRIFT/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 600000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 6, + "initialLeverage": 2, + "notionalCap": 600000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 54575.0 } }, { "tier": 7.0, "symbol": "DRIFT/USDT:USDT", "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 7500000, - "notionalFloor": 500000, - "maintMarginRatio": 0.25, - "cum": 54625.0 - } - }, - { - "tier": 8.0, - "symbol": "DRIFT/USDT:USDT", - "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 12500000.0, + "minNotional": 600000.0, + "maxNotional": 650000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 7, "initialLeverage": 1, - "notionalCap": 12500000, - "notionalFloor": 7500000, + "notionalCap": 650000, + "notionalFloor": 600000, "maintMarginRatio": 0.5, - "cum": 1929625.0 + "cum": 204575.0 } } ], @@ -30902,14 +31522,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 5, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.1, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -30919,15 +31539,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": 2, - "initialLeverage": 4, + "initialLeverage": 25, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.125, - "cum": 125.0 + "maintMarginRatio": 0.02, + "cum": 25.0 } }, { @@ -30935,50 +31555,118 @@ "symbol": "EDGE/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 30000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 3, - "initialLeverage": 3, - "notionalCap": 30000, + "initialLeverage": 20, + "notionalCap": 25000, "notionalFloor": 10000, - "maintMarginRatio": 0.1667, - "cum": 542.0 + "maintMarginRatio": 0.025, + "cum": 75.0 } }, { "tier": 4.0, "symbol": "EDGE/USDT:USDT", "currency": "USDT", - "minNotional": 30000.0, - "maxNotional": 80000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 25000.0, + "maxNotional": 62500.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 4, - "initialLeverage": 2, - "notionalCap": 80000, - "notionalFloor": 30000, - "maintMarginRatio": 0.25, - "cum": 3041.0 + "initialLeverage": 10, + "notionalCap": 62500, + "notionalFloor": 25000, + "maintMarginRatio": 0.05, + "cum": 700.0 } }, { "tier": 5.0, "symbol": "EDGE/USDT:USDT", "currency": "USDT", - "minNotional": 80000.0, - "maxNotional": 200000.0, + "minNotional": 62500.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 5, + "initialLeverage": 5, + "notionalCap": 125000, + "notionalFloor": 62500, + "maintMarginRatio": 0.1, + "cum": 3825.0 + } + }, + { + "tier": 6.0, + "symbol": "EDGE/USDT:USDT", + "currency": "USDT", + "minNotional": 125000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 6, + "initialLeverage": 4, + "notionalCap": 250000, + "notionalFloor": 125000, + "maintMarginRatio": 0.125, + "cum": 6950.0 + } + }, + { + "tier": 7.0, + "symbol": "EDGE/USDT:USDT", + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 7, + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 17375.0 + } + }, + { + "tier": 8.0, + "symbol": "EDGE/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 8, + "initialLeverage": 2, + "notionalCap": 7500000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 59025.0 + } + }, + { + "tier": 9.0, + "symbol": "EDGE/USDT:USDT", + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 5, + "bracket": 9, "initialLeverage": 1, - "notionalCap": 200000, - "notionalFloor": 80000, + "notionalCap": 12500000, + "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 23041.0 + "cum": 1934025.0 } } ], @@ -34082,6 +34770,144 @@ } } ], + "ETH/USDT:USDT-260925": [ + { + "tier": 1.0, + "symbol": "ETH/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, + "info": { + "bracket": 1, + "initialLeverage": 50, + "notionalCap": 50000, + "notionalFloor": 0, + "maintMarginRatio": 0.01, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "ETH/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 375000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": 2, + "initialLeverage": 25, + "notionalCap": 375000, + "notionalFloor": 50000, + "maintMarginRatio": 0.02, + "cum": 500.0 + } + }, + { + "tier": 3.0, + "symbol": "ETH/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 375000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 3, + "initialLeverage": 10, + "notionalCap": 2000000, + "notionalFloor": 375000, + "maintMarginRatio": 0.05, + "cum": 11750.0 + } + }, + { + "tier": 4.0, + "symbol": "ETH/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 2000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 4, + "initialLeverage": 5, + "notionalCap": 4000000, + "notionalFloor": 2000000, + "maintMarginRatio": 0.1, + "cum": 111750.0 + } + }, + { + "tier": 5.0, + "symbol": "ETH/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 4000000.0, + "maxNotional": 10000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 5, + "initialLeverage": 4, + "notionalCap": 10000000, + "notionalFloor": 4000000, + "maintMarginRatio": 0.125, + "cum": 211750.0 + } + }, + { + "tier": 6.0, + "symbol": "ETH/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 10000000.0, + "maxNotional": 20000000.0, + "maintenanceMarginRate": 0.15, + "maxLeverage": 3.0, + "info": { + "bracket": 6, + "initialLeverage": 3, + "notionalCap": 20000000, + "notionalFloor": 10000000, + "maintMarginRatio": 0.15, + "cum": 461750.0 + } + }, + { + "tier": 7.0, + "symbol": "ETH/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 20000000.0, + "maxNotional": 40000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 7, + "initialLeverage": 2, + "notionalCap": 40000000, + "notionalFloor": 20000000, + "maintMarginRatio": 0.25, + "cum": 2461750.0 + } + }, + { + "tier": 8.0, + "symbol": "ETH/USDT:USDT-260925", + "currency": "USDT", + "minNotional": 40000000.0, + "maxNotional": 120000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 8, + "initialLeverage": 1, + "notionalCap": 120000000, + "notionalFloor": 40000000, + "maintMarginRatio": 0.5, + "cum": 12461750.0 + } + } + ], "ETHFI/USDC:USDC": [ { "tier": 1.0, @@ -54241,13 +55067,13 @@ "symbol": "M/USDT:USDT", "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 20000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": 2, "initialLeverage": 20, - "notionalCap": 20000, + "notionalCap": 10000, "notionalFloor": 5000, "maintMarginRatio": 0.025, "cum": 50.0 @@ -54257,58 +55083,58 @@ "tier": 3.0, "symbol": "M/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 50000.0, + "minNotional": 10000.0, + "maxNotional": 40000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": 3, "initialLeverage": 10, - "notionalCap": 50000, - "notionalFloor": 20000, + "notionalCap": 40000, + "notionalFloor": 10000, "maintMarginRatio": 0.05, - "cum": 550.0 + "cum": 300.0 } }, { "tier": 4.0, "symbol": "M/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 125000.0, + "minNotional": 40000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": 4, "initialLeverage": 5, - "notionalCap": 125000, - "notionalFloor": 50000, + "notionalCap": 100000, + "notionalFloor": 40000, "maintMarginRatio": 0.1, - "cum": 3050.0 + "cum": 2300.0 } }, { "tier": 5.0, "symbol": "M/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, + "minNotional": 100000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": 5, "initialLeverage": 4, - "notionalCap": 250000, - "notionalFloor": 125000, + "notionalCap": 200000, + "notionalFloor": 100000, "maintMarginRatio": 0.125, - "cum": 6175.0 + "cum": 4800.0 } }, { "tier": 6.0, "symbol": "M/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 200000.0, "maxNotional": 500000.0, "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, @@ -54316,9 +55142,9 @@ "bracket": 6, "initialLeverage": 3, "notionalCap": 500000, - "notionalFloor": 250000, + "notionalFloor": 200000, "maintMarginRatio": 0.1667, - "cum": 16600.0 + "cum": 13140.0 } }, { @@ -54335,7 +55161,7 @@ "notionalCap": 2500000, "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 58250.0 + "cum": 54790.0 } }, { @@ -54352,7 +55178,7 @@ "notionalCap": 3000000, "notionalFloor": 2500000, "maintMarginRatio": 0.5, - "cum": 683250.0 + "cum": 679790.0 } } ], @@ -59925,6 +60751,178 @@ } } ], + "NATGAS/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "NATGAS/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.005, + "maxLeverage": 100.0, + "info": { + "bracket": 1, + "initialLeverage": 100, + "notionalCap": 50000, + "notionalFloor": 0, + "maintMarginRatio": 0.005, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "NATGAS/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.0065, + "maxLeverage": 75.0, + "info": { + "bracket": 2, + "initialLeverage": 75, + "notionalCap": 400000, + "notionalFloor": 50000, + "maintMarginRatio": 0.0065, + "cum": 75.0 + } + }, + { + "tier": 3.0, + "symbol": "NATGAS/USDT:USDT", + "currency": "USDT", + "minNotional": 400000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 50.0, + "info": { + "bracket": 3, + "initialLeverage": 50, + "notionalCap": 1000000, + "notionalFloor": 400000, + "maintMarginRatio": 0.01, + "cum": 1475.0 + } + }, + { + "tier": 4.0, + "symbol": "NATGAS/USDT:USDT", + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 4000000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": 4, + "initialLeverage": 25, + "notionalCap": 4000000, + "notionalFloor": 1000000, + "maintMarginRatio": 0.02, + "cum": 11475.0 + } + }, + { + "tier": 5.0, + "symbol": "NATGAS/USDT:USDT", + "currency": "USDT", + "minNotional": 4000000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": 5, + "initialLeverage": 20, + "notionalCap": 8000000, + "notionalFloor": 4000000, + "maintMarginRatio": 0.025, + "cum": 31475.0 + } + }, + { + "tier": 6.0, + "symbol": "NATGAS/USDT:USDT", + "currency": "USDT", + "minNotional": 8000000.0, + "maxNotional": 40000000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 6, + "initialLeverage": 10, + "notionalCap": 40000000, + "notionalFloor": 8000000, + "maintMarginRatio": 0.05, + "cum": 231475.0 + } + }, + { + "tier": 7.0, + "symbol": "NATGAS/USDT:USDT", + "currency": "USDT", + "minNotional": 40000000.0, + "maxNotional": 80000000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 7, + "initialLeverage": 5, + "notionalCap": 80000000, + "notionalFloor": 40000000, + "maintMarginRatio": 0.1, + "cum": 2231475.0 + } + }, + { + "tier": 8.0, + "symbol": "NATGAS/USDT:USDT", + "currency": "USDT", + "minNotional": 80000000.0, + "maxNotional": 100000000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 8, + "initialLeverage": 4, + "notionalCap": 100000000, + "notionalFloor": 80000000, + "maintMarginRatio": 0.125, + "cum": 4231475.0 + } + }, + { + "tier": 9.0, + "symbol": "NATGAS/USDT:USDT", + "currency": "USDT", + "minNotional": 100000000.0, + "maxNotional": 200000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 9, + "initialLeverage": 2, + "notionalCap": 200000000, + "notionalFloor": 100000000, + "maintMarginRatio": 0.25, + "cum": 16731475.0 + } + }, + { + "tier": 10.0, + "symbol": "NATGAS/USDT:USDT", + "currency": "USDT", + "minNotional": 200000000.0, + "maxNotional": 400000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 10, + "initialLeverage": 1, + "notionalCap": 400000000, + "notionalFloor": 200000000, + "maintMarginRatio": 0.5, + "cum": 66731475.0 + } + } + ], "NEAR/USDC:USDC": [ { "tier": 1.0, @@ -69475,6 +70473,127 @@ } } ], + "PRL/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "PRL/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": 1, + "initialLeverage": 20, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.025, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "PRL/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 2, + "initialLeverage": 10, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.05, + "cum": 125.0 + } + }, + { + "tier": 3.0, + "symbol": "PRL/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 3, + "initialLeverage": 5, + "notionalCap": 50000, + "notionalFloor": 10000, + "maintMarginRatio": 0.1, + "cum": 625.0 + } + }, + { + "tier": 4.0, + "symbol": "PRL/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 4, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 50000, + "maintMarginRatio": 0.125, + "cum": 1875.0 + } + }, + { + "tier": 5.0, + "symbol": "PRL/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 5, + "initialLeverage": 3, + "notionalCap": 250000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 6045.0 + } + }, + { + "tier": 6.0, + "symbol": "PRL/USDT:USDT", + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 6, + "initialLeverage": 2, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.25, + "cum": 26870.0 + } + }, + { + "tier": 7.0, + "symbol": "PRL/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 7, + "initialLeverage": 1, + "notionalCap": 800000, + "notionalFloor": 500000, + "maintMarginRatio": 0.5, + "cum": 151870.0 + } + } + ], "PROM/USDT:USDT": [ { "tier": 1.0, @@ -95110,6 +96229,161 @@ } } ], + "XAUT/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "XAUT/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": 1, + "initialLeverage": 50, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.015, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "XAUT/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": 2, + "initialLeverage": 25, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.02, + "cum": 25.0 + } + }, + { + "tier": 3.0, + "symbol": "XAUT/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": 3, + "initialLeverage": 20, + "notionalCap": 25000, + "notionalFloor": 10000, + "maintMarginRatio": 0.025, + "cum": 75.0 + } + }, + { + "tier": 4.0, + "symbol": "XAUT/USDT:USDT", + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 62500.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 4, + "initialLeverage": 10, + "notionalCap": 62500, + "notionalFloor": 25000, + "maintMarginRatio": 0.05, + "cum": 700.0 + } + }, + { + "tier": 5.0, + "symbol": "XAUT/USDT:USDT", + "currency": "USDT", + "minNotional": 62500.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 5, + "initialLeverage": 5, + "notionalCap": 125000, + "notionalFloor": 62500, + "maintMarginRatio": 0.1, + "cum": 3825.0 + } + }, + { + "tier": 6.0, + "symbol": "XAUT/USDT:USDT", + "currency": "USDT", + "minNotional": 125000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 6, + "initialLeverage": 4, + "notionalCap": 250000, + "notionalFloor": 125000, + "maintMarginRatio": 0.125, + "cum": 6950.0 + } + }, + { + "tier": 7.0, + "symbol": "XAUT/USDT:USDT", + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 7, + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 17375.0 + } + }, + { + "tier": 8.0, + "symbol": "XAUT/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 8, + "initialLeverage": 2, + "notionalCap": 7500000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 59025.0 + } + }, + { + "tier": 9.0, + "symbol": "XAUT/USDT:USDT", + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 12500000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 9, + "initialLeverage": 1, + "notionalCap": 12500000, + "notionalFloor": 7500000, + "maintMarginRatio": 0.5, + "cum": 1934025.0 + } + } + ], "XCN/USDT:USDT": [ { "tier": 1.0, From f1fc3e14514fe040b153bcbe0eb78a058ccaca0e Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 2 Apr 2026 07:04:39 +0200 Subject: [PATCH 029/315] chore: remove temporary exclude-newer-package sections --- pyproject.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index eaab3445e..59824d5ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -222,9 +222,7 @@ exclude-newer = "1 week" [tool.uv.exclude-newer-package] ccxt = false -cryptography = "3 days" -requests = "5 days" -build= "5 days" +aiohttp = "3 days" [tool.ruff] line-length = 100 From f96f9b760110a7370abb3275c06752b3615036a5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 2 Apr 2026 07:26:07 +0200 Subject: [PATCH 030/315] chore: move futures only flags to ft_has_futures --- freqtrade/exchange/bitget.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/bitget.py b/freqtrade/exchange/bitget.py index 277ae5532..72aaf44cb 100644 --- a/freqtrade/exchange/bitget.py +++ b/freqtrade/exchange/bitget.py @@ -34,16 +34,16 @@ class Bitget(Exchange): "stoploss_query_requires_stop_flag": True, "ohlcv_candle_limit": 200, # 200 for historical candles, 1000 for recent ones. "order_time_in_force": ["GTC", "FOK", "IOC", "PO"], + } + _ft_has_futures: FtHas = { + "funding_fee_candle_limit": 100, + "has_delisting": True, "stop_price_type_field": "triggerType", "stop_price_type_value_mapping": { PriceType.LAST: "fill_price", PriceType.MARK: "mark_price", }, } - _ft_has_futures: FtHas = { - "funding_fee_candle_limit": 100, - "has_delisting": True, - } _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ (TradingMode.SPOT, MarginMode.NONE), From b5d400c50da533a494c7f1bbbf5f86abb9ab838f Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 3 Apr 2026 11:56:04 +0200 Subject: [PATCH 031/315] chore: increase aiohttp exclude to 5 days --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 59824d5ca..2cdc5b214 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -222,7 +222,7 @@ exclude-newer = "1 week" [tool.uv.exclude-newer-package] ccxt = false -aiohttp = "3 days" +aiohttp = "5 days" [tool.ruff] line-length = 100 From 4f600eb078af0679e541647e8f687d305a2db64a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 09:57:50 +0000 Subject: [PATCH 032/315] chore(deps): bump aiohttp from 3.13.3 to 3.13.4 --- updated-dependencies: - dependency-name: aiohttp dependency-version: 3.13.4 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1f6d07a6a..aa5c3ef7c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ technical==1.5.4 ccxt==4.5.45 cryptography==46.0.6 -aiohttp==3.13.3 +aiohttp==3.13.4 SQLAlchemy==2.0.48 python-telegram-bot==22.7 # can't be hard-pinned due to telegram-bot pinning httpx with ~ From 8a98b6172c5f97b40d5aa03741264a0533a9f83c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 08:40:54 +0200 Subject: [PATCH 033/315] refactor: use fstrings for regular log output --- freqtrade/rpc/rpc_manager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index 0a4f48a35..72f88af29 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -59,7 +59,7 @@ class RPCManager: logger.info("Cleaning up rpc modules ...") while self.registered_modules: mod = self.registered_modules.pop() - logger.info("Cleaning up rpc.%s ...", mod.name) + logger.info(f"Cleaning up rpc.{mod.name} ...") mod.cleanup() del mod @@ -73,7 +73,7 @@ class RPCManager: } """ if msg.get("type") not in NO_ECHO_MESSAGES: - logger.info("Sending rpc message: %s", msg) + logger.info(f"Sending rpc message: {msg}") for mod in self.registered_modules: logger.debug("Forwarding message to rpc.%s", mod.name) try: @@ -81,7 +81,7 @@ class RPCManager: except NotImplementedError: logger.error(f"Message type '{msg['type']}' not implemented by handler {mod.name}.") except Exception: - logger.exception("Exception occurred within RPC module %s", mod.name) + logger.exception(f"Exception occurred within RPC module {mod.name}") def process_msg_queue(self, queue: deque) -> None: """ @@ -89,7 +89,7 @@ class RPCManager: """ while queue: msg = queue.popleft() - logger.info("Sending rpc strategy_msg: %s", msg) + logger.info(f"Sending rpc strategy_msg: {msg}") for mod in self.registered_modules: if mod._config.get(mod.name, {}).get("allow_custom_messages", False): mod.send_msg( From 65f494be266431bf8a2ff4f1e44fb01301552dfb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 08:41:08 +0200 Subject: [PATCH 034/315] fix: don't raise freqAI in pre-mature shutdown scenarios --- freqtrade/strategy/interface.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 6e32039c9..13f0ed4b4 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -222,7 +222,8 @@ class IStrategy(ABC, HyperStrategyMixin): """ Clean up FreqAI and child threads """ - self.freqai.shutdown() + if getattr(self, "freqai", None): + self.freqai.shutdown() @abstractmethod def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: From 1ebd75b32ab2d2dca9b349b70fc1b9ad37f0c183 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 08:42:44 +0200 Subject: [PATCH 035/315] fix: improve freqtradebot cleanup resilience --- freqtrade/freqtradebot.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 28560a460..8e518cf1c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -132,7 +132,7 @@ class FreqtradeBot(LoggingMixin): self.strategy.wallets = self.wallets # Init ExternalMessageConsumer if enabled - self.emc = ( + self.emc: ExternalMessageConsumer | None = ( ExternalMessageConsumer(self.config, self.dataprovider) if self.config.get("external_message_consumer", {}).get("enabled", False) else None @@ -213,10 +213,12 @@ class FreqtradeBot(LoggingMixin): finally: self.strategy.ft_bot_cleanup() - self.rpc.cleanup() - if self.emc: + if getattr(self, "rpc", None): + self.rpc.cleanup() + if getattr(self, "emc", None): self.emc.shutdown() - self.exchange.close() + if getattr(self, "exchange", None): + self.exchange.close() try: Trade.commit() except Exception: From e3c316f3118ff0e6690103279deffd645965fb4f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 08:51:04 +0200 Subject: [PATCH 036/315] fix: Graceful shutdown in case of failed initialization --- freqtrade/freqtradebot.py | 189 +++++++++++++++++++------------------- 1 file changed, 97 insertions(+), 92 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 8e518cf1c..a9cc8cd1f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -92,100 +92,105 @@ class FreqtradeBot(LoggingMixin): exchange_config: ExchangeConfig = deepcopy(config["exchange"]) # Remove credentials from original exchange config to avoid accidental credential exposure remove_exchange_credentials(config["exchange"], True) - - self.exchange = ExchangeResolver.load_exchange( - self.config, exchange_config=exchange_config, load_leverage_tiers=True - ) - - self.strategy: IStrategy = StrategyResolver.load_strategy(self.config) - - # Check config consistency here since strategies can set certain options - validate_config_consistency(config) - # Re-validate exchange compatibility - self.exchange.validate_config(self.config) - - init_db(self.config["db_url"]) - - self.wallets = Wallets(self.config, self.exchange) - - PairLocks.timeframe = self.config["timeframe"] - - self.trading_mode: TradingMode = self.config.get("trading_mode", TradingMode.SPOT) - self.margin_mode: MarginMode = self.config.get("margin_mode", MarginMode.NONE) - self.last_process: datetime | None = None - - # RPC runs in separate threads, can start handling external commands just after - # initialization, even before Freqtradebot has a chance to start its throttling, - # so anything in the Freqtradebot instance should be ready (initialized), including - # the initial state of the bot. - # Keep this at the end of this initialization method. - self.rpc: RPCManager = RPCManager(self) - - self.dataprovider = DataProvider(self.config, self.exchange, rpc=self.rpc) - self.pairlists = PairListManager(self.exchange, self.config, self.dataprovider) - - self.dataprovider.add_pairlisthandler(self.pairlists) - - # Attach Dataprovider to strategy instance - self.strategy.dp = self.dataprovider - # Attach Wallets to strategy instance - self.strategy.wallets = self.wallets - - # Init ExternalMessageConsumer if enabled - self.emc: ExternalMessageConsumer | None = ( - ExternalMessageConsumer(self.config, self.dataprovider) - if self.config.get("external_message_consumer", {}).get("enabled", False) - else None - ) - - logger.info("Starting initial pairlist refresh") - with MeasureTime( - lambda duration, _: logger.info(f"Initial Pairlist refresh took {duration:.2f}s"), 0 - ): - self.active_pair_whitelist = self._refresh_active_whitelist() - - # Set initial bot state from config - initial_state = self.config.get("initial_state") - self.state = State[initial_state.upper()] if initial_state else State.STOPPED - - # Protect exit-logic from forcesell and vice versa - self._exit_lock = Lock() - timeframe_secs = timeframe_to_seconds(self.strategy.timeframe) - self._exit_reason_cache = PeriodicCache(100, ttl=timeframe_secs) - LoggingMixin.__init__(self, logger, timeframe_secs) - - self._schedule = Scheduler() - - if self.trading_mode == TradingMode.FUTURES: - - def update(): - self.update_funding_fees() - self.update_all_liquidation_prices() - self.wallets.update() - - # This would be more efficient if scheduled in utc time, and performed at each - # funding interval, specified by funding_fee_times on the exchange classes - # However, this reduces the precision - and might therefore lead to problems. - for time_slot in range(0, 24): - for minutes in [1, 31]: - t = str(time(time_slot, minutes, 2)) - self._schedule.every().day.at(t).do(update) - - self._schedule.every().day.at("00:02").do(self.exchange.ws_connection_reset) - - self.strategy.ft_bot_start() - # Initialize protections AFTER bot start - otherwise parameters are not loaded. - self.protections = ProtectionManager(self.config, self.strategy.protections) - - def log_took_too_long(duration: float, time_limit: float): - logger.warning( - f"Strategy analysis took {duration:.2f}s, more than 25% of the timeframe " - f"({time_limit:.2f}s). This can lead to delayed orders and missed signals." - "Consider either reducing the amount of work your strategy performs " - "or reduce the amount of pairs in the Pairlist." + try: + self.exchange = ExchangeResolver.load_exchange( + self.config, exchange_config=exchange_config, load_leverage_tiers=True ) - self._measure_execution = MeasureTime(log_took_too_long, timeframe_secs * 0.25) + self.strategy: IStrategy = StrategyResolver.load_strategy(self.config) + + # Check config consistency here since strategies can set certain options + validate_config_consistency(config) + # Re-validate exchange compatibility + self.exchange.validate_config(self.config) + + init_db(self.config["db_url"]) + + self.wallets = Wallets(self.config, self.exchange) + + PairLocks.timeframe = self.config["timeframe"] + + self.trading_mode: TradingMode = self.config.get("trading_mode", TradingMode.SPOT) + self.margin_mode: MarginMode = self.config.get("margin_mode", MarginMode.NONE) + self.last_process: datetime | None = None + + # RPC runs in separate threads, can start handling external commands just after + # initialization, even before Freqtradebot has a chance to start its throttling, + # so anything in the Freqtradebot instance should be ready (initialized), including + # the initial state of the bot. + # Keep this at the end of this initialization method. + self.rpc: RPCManager = RPCManager(self) + + self.dataprovider = DataProvider(self.config, self.exchange, rpc=self.rpc) + self.pairlists = PairListManager(self.exchange, self.config, self.dataprovider) + + self.dataprovider.add_pairlisthandler(self.pairlists) + + # Attach Dataprovider to strategy instance + self.strategy.dp = self.dataprovider + # Attach Wallets to strategy instance + self.strategy.wallets = self.wallets + + # Init ExternalMessageConsumer if enabled + self.emc: ExternalMessageConsumer | None = ( + ExternalMessageConsumer(self.config, self.dataprovider) + if self.config.get("external_message_consumer", {}).get("enabled", False) + else None + ) + + logger.info("Starting initial pairlist refresh") + with MeasureTime( + lambda duration, _: logger.info(f"Initial Pairlist refresh took {duration:.2f}s"), 0 + ): + self.active_pair_whitelist = self._refresh_active_whitelist() + + # Set initial bot state from config + initial_state = self.config.get("initial_state") + self.state = State[initial_state.upper()] if initial_state else State.STOPPED + + # Protect exit-logic from forcesell and vice versa + self._exit_lock = Lock() + timeframe_secs = timeframe_to_seconds(self.strategy.timeframe) + self._exit_reason_cache = PeriodicCache(100, ttl=timeframe_secs) + LoggingMixin.__init__(self, logger, timeframe_secs) + + self._schedule = Scheduler() + + if self.trading_mode == TradingMode.FUTURES: + + def update(): + self.update_funding_fees() + self.update_all_liquidation_prices() + self.wallets.update() + + # This would be more efficient if scheduled in utc time, and performed at each + # funding interval, specified by funding_fee_times on the exchange classes + # However, this reduces the precision - and might therefore lead to problems. + for time_slot in range(0, 24): + for minutes in [1, 31]: + t = str(time(time_slot, minutes, 2)) + self._schedule.every().day.at(t).do(update) + + self._schedule.every().day.at("00:02").do(self.exchange.ws_connection_reset) + + self.strategy.ft_bot_start() + # Initialize protections AFTER bot start - otherwise parameters are not loaded. + self.protections = ProtectionManager(self.config, self.strategy.protections) + + def log_took_too_long(duration: float, time_limit: float): + logger.warning( + f"Strategy analysis took {duration:.2f}s, more than 25% of the timeframe " + f"({time_limit:.2f}s). This can lead to delayed orders and missed signals." + "Consider either reducing the amount of work your strategy performs " + "or reduce the amount of pairs in the Pairlist." + ) + + self._measure_execution = MeasureTime(log_took_too_long, timeframe_secs * 0.25) + + except Exception as e: + # Graceful shutdown in case of failed initialization. + self.cleanup() + raise e from e def notify_status(self, msg: str, msg_type=RPCMessageType.STATUS) -> None: """ From 4ddb7fd43f688c3a632fd80127d8eadc2b6848c9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 09:02:52 +0200 Subject: [PATCH 037/315] chore: clarify delist-filter exception --- freqtrade/plugins/pairlist/DelistFilter.py | 5 +++-- tests/plugins/test_pairlist.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/plugins/pairlist/DelistFilter.py b/freqtrade/plugins/pairlist/DelistFilter.py index 076751aa6..8a2491c7d 100644 --- a/freqtrade/plugins/pairlist/DelistFilter.py +++ b/freqtrade/plugins/pairlist/DelistFilter.py @@ -23,9 +23,10 @@ class DelistFilter(IPairList): self._max_days_from_now = self._pairlistconfig.get("max_days_from_now", 0) if self._max_days_from_now < 0: raise ConfigurationError("DelistFilter requires max_days_from_now to be >= 0") - if not self._exchange._ft_has["has_delisting"]: + if not self._exchange.get_option("has_delisting"): raise ConfigurationError( - "DelistFilter doesn't support this exchange and trading mode combination.", + f"DelistFilter doesn't support {self._exchange.name} in " + f"{self._exchange.trading_mode} mode." ) def short_desc(self) -> str: diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 5d43e912f..9d2c4bed8 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -2850,10 +2850,10 @@ def test_backtesting_modes( def test_DelistFilter_error(whitelist_conf) -> None: whitelist_conf["pairlists"] = [{"method": "StaticPairList"}, {"method": "DelistFilter"}] exchange_mock = MagicMock() - exchange_mock._ft_has = {"has_delisting": False} + exchange_mock.get_option = MagicMock(return_value=False) with pytest.raises( OperationalException, - match=r"DelistFilter doesn't support this exchange and trading mode combination\.", + match=r"DelistFilter doesn't support .* in .* mode\.", ): PairListManager(exchange_mock, whitelist_conf, MagicMock()) From 3a76235c489a26b08fff6ba1d727492d31841bb2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 09:09:16 +0200 Subject: [PATCH 038/315] fix: improved check for emc due to type --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a9cc8cd1f..2b4d6c8ff 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -220,7 +220,7 @@ class FreqtradeBot(LoggingMixin): if getattr(self, "rpc", None): self.rpc.cleanup() - if getattr(self, "emc", None): + if hasattr(self, "emc") and self.emc: self.emc.shutdown() if getattr(self, "exchange", None): self.exchange.close() From a3e7ee989501a82db2a871dc446b4c698b8acd90 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 26 Apr 2025 08:44:32 +0200 Subject: [PATCH 039/315] feat: capture wallet state per candle in backtesting --- freqtrade/optimize/backtesting.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index a95b29005..138fcce50 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -137,6 +137,7 @@ class Backtesting: } self.rejected_dict: dict[str, list] = {} self.starting_balance: float = 0.0 + self.wallet_captures: list = [] self._exchange_name = self.config["exchange"]["name"] self.__initial_backtest = exchange is None @@ -1603,6 +1604,7 @@ class Backtesting: pair_detail_cache: dict[str, list[tuple]] = {} pair_tradedir_cache: dict[str, LongShort | None] = {} pairs_with_open_trades = [t.pair for t in LocalTrade.bt_trades_open] + self._capture_wallet(current_time, self.strategy.config["stake_currency"], 1) for current_time_det, is_first, has_detail, idx, pair in self._time_pair_generator_det( current_time, pairs @@ -1627,6 +1629,7 @@ class Backtesting: ) trade_dir = self.check_for_trade_entry(row) pair_tradedir_cache[pair] = trade_dir + self._capture_wallet(current_time, pair.split("/")[0], row[OPEN_IDX]) else: # Detail candle - from cache. @@ -1680,6 +1683,15 @@ class Backtesting: yield current_time_det, pair, row, is_last_row, trade_dir self.progress.increment() + def _capture_wallet(self, current_time: datetime, currency: str, price: float) -> None: + """ + Capture the current wallet state. + """ + if self.dataprovider.runmode != RunMode.BACKTEST: + return + if total := self.wallets.get_total(currency): + self.wallet_captures.append((current_time, currency, price, total)) + def backtest( self, processed: dict, start_date: datetime, end_date: datetime ) -> BacktestContentTypeIcomplete: From 11cb3ef41609e2c662fafa1f7c1d05c3a41e7266 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 26 Apr 2025 08:45:54 +0200 Subject: [PATCH 040/315] feat: reset wallet_captures list in Backtesting --- freqtrade/optimize/backtesting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 138fcce50..797e47786 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -452,6 +452,7 @@ class Backtesting: self.replaced_entry_orders = 0 self.canceled_exit_orders = 0 self.replaced_exit_orders = 0 + self.wallet_captures = [] self.dataprovider.clear_cache() if enable_protections: self._load_protections(self.strategy) From 10cc857c51e9b560ee414909f8f8f7ae925bbeee Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 26 Apr 2025 09:14:17 +0200 Subject: [PATCH 041/315] feat: add wallet to dataframe conversion --- freqtrade/ft_types/backtest_result_type.py | 1 + freqtrade/optimize/backtesting.py | 2 ++ freqtrade/optimize/optimize_reports/__init__.py | 1 + .../optimize/optimize_reports/optimize_reports.py | 14 ++++++++++++++ 4 files changed, 18 insertions(+) diff --git a/freqtrade/ft_types/backtest_result_type.py b/freqtrade/ft_types/backtest_result_type.py index b253231a1..768d13517 100644 --- a/freqtrade/ft_types/backtest_result_type.py +++ b/freqtrade/ft_types/backtest_result_type.py @@ -55,6 +55,7 @@ class BacktestContentTypeIcomplete(TypedDict, total=False): backtest_start_time: int backtest_end_time: int run_id: str + wallet_summary: DataFrame class BacktestContentType(BacktestContentTypeIcomplete, total=True): diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 797e47786..32c145e24 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -51,6 +51,7 @@ 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 ( + convert_bt_wallet_collection, generate_backtest_stats, generate_rejected_signals, generate_trade_signal_candles, @@ -1752,6 +1753,7 @@ class Backtesting: "canceled_entry_orders": self.canceled_entry_orders, "replaced_entry_orders": self.replaced_entry_orders, "final_balance": self.wallets.get_total(self.strategy.config["stake_currency"]), + "wallet_summary": convert_bt_wallet_collection(self.wallet_captures), } def backtest_one_strategy( diff --git a/freqtrade/optimize/optimize_reports/__init__.py b/freqtrade/optimize/optimize_reports/__init__.py index 5cf8e51ad..a41a8ebbf 100644 --- a/freqtrade/optimize/optimize_reports/__init__.py +++ b/freqtrade/optimize/optimize_reports/__init__.py @@ -12,6 +12,7 @@ from freqtrade.optimize.optimize_reports.bt_output import ( ) from freqtrade.optimize.optimize_reports.bt_storage import store_backtest_results from freqtrade.optimize.optimize_reports.optimize_reports import ( + convert_bt_wallet_collection, generate_all_periodic_breakdown_stats, generate_backtest_stats, generate_daily_stats, diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index 57b7740d8..2f640bcbf 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -29,6 +29,20 @@ from freqtrade.util import decimals_per_coin, fmt_coin, format_duration, get_dry logger = logging.getLogger(__name__) +def convert_bt_wallet_collection(wallet_captures: list[tuple]) -> DataFrame: + """ + Convert the wallet capture list to a DataFrame. + Assumes the wallet_captures list contains tuples with the following structure: + (date, currency, price, balance). + """ + if len(wallet_captures) == 0: + return [] + return DataFrame( + wallet_captures, + columns=["date", "currency", "price", "balance"], + ) + + def generate_trade_signal_candles( preprocessed_df: dict[str, DataFrame], bt_results: BacktestContentType, date_col: str ) -> dict[str, DataFrame]: From ee745551a2f31e5e4bc79aa9e1882de5ccc02051 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 26 Apr 2025 09:34:10 +0200 Subject: [PATCH 042/315] feat: store wallet_summary --- freqtrade/optimize/backtesting.py | 1 + freqtrade/optimize/optimize_reports/bt_storage.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 32c145e24..1ac5fa989 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -1882,6 +1882,7 @@ class Backtesting: dt_appendix, market_change_data=combined_res, analysis_results=self.analysis_results, + wallet_summary={s: x["wallet_summary"] for s, x in self.all_bt_content.items()}, strategy_files={s.get_strategy_name(): s.__file__ for s in self.strategylist}, ) diff --git a/freqtrade/optimize/optimize_reports/bt_storage.py b/freqtrade/optimize/optimize_reports/bt_storage.py index ef73d4721..c4f20e3e4 100644 --- a/freqtrade/optimize/optimize_reports/bt_storage.py +++ b/freqtrade/optimize/optimize_reports/bt_storage.py @@ -52,6 +52,7 @@ def store_backtest_results( dtappendix: str, *, market_change_data: DataFrame | None = None, + wallet_summary: dict[str, DataFrame] | None = None, analysis_results: dict[str, dict[str, DataFrame]] | None = None, strategy_files: dict[str, str] | None = None, ) -> Path: @@ -123,6 +124,15 @@ def store_backtest_results( market_change_buf.seek(0) zipf.writestr(market_change_name, market_change_buf.getvalue()) + # Add wallet summary if present + if wallet_summary is not None: + for strategy, df in wallet_summary.items(): + wallet_name = f"{base_filename.stem}_{strategy}_wallet.feather" + wallet_buf = BytesIO() + df.reset_index().to_feather(wallet_buf, compression_level=9, compression="lz4") + wallet_buf.seek(0) + zipf.writestr(wallet_name, wallet_buf.getvalue()) + # Add analysis results if present and running in backtest mode if ( config.get("export", "none") == "signals" From 21269b8a8d48d74f231b4adc21a2a5f175700d99 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 26 Apr 2025 10:16:00 +0200 Subject: [PATCH 043/315] feat: add backtest/.../wallets endpoint --- freqtrade/data/btanalysis/bt_fileutils.py | 14 ++++++++++++ freqtrade/rpc/api_server/api_backtest.py | 28 +++++++++++++++++++++++ freqtrade/rpc/api_server/api_schemas.py | 6 +++++ freqtrade/rpc/api_server/api_v1.py | 3 ++- 4 files changed, 50 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/btanalysis/bt_fileutils.py b/freqtrade/data/btanalysis/bt_fileutils.py index e1c0ea64c..7d29ab9a2 100644 --- a/freqtrade/data/btanalysis/bt_fileutils.py +++ b/freqtrade/data/btanalysis/bt_fileutils.py @@ -312,6 +312,20 @@ def get_backtest_market_change(filename: Path, include_ts: bool = True) -> pd.Da return df +def get_backtest_wallet_change(filename: Path, strategy_name: str) -> pd.DataFrame: + """ + Read backtest wallet change file. + :param filename: Path to the backtest result zip file + :param strategy_name: Name of the strategy to load + :return: DataFrame with wallet change data + """ + data = load_file_from_zip(filename, f"{filename.stem}_{strategy_name}_wallet.feather") + df = pd.read_feather(BytesIO(data)) + + df.loc[:, "__date_ts"] = df.loc[:, "date"].astype(np.int64) // 1000 // 1000 + return df + + def find_existing_backtest_stats( dirname: Path | str, run_ids: dict[str, str], min_backtest_date: datetime | None = None ) -> dict[str, Any]: diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 428fea1c9..92339c7d2 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -16,6 +16,7 @@ from freqtrade.data.btanalysis import ( get_backtest_market_change, get_backtest_result, get_backtest_resultlist, + get_backtest_wallet_change, load_and_merge_backtest_result, update_backtest_metadata, ) @@ -29,6 +30,7 @@ from freqtrade.rpc.api_server.api_schemas import ( BacktestMetadataUpdate, BacktestRequest, BacktestResponse, + BacktestWalletsSummary, ) from freqtrade.rpc.api_server.deps import get_config, verify_strategy from freqtrade.rpc.api_server.webserver_bgwork import ApiBG @@ -354,3 +356,29 @@ def api_get_backtest_market_change(file: str, config=Depends(get_config)): "data": df.values.tolist(), "length": len(df), } + + +@router.get( + "/backtest/history/{file}/{strategy}/wallet", + response_model=BacktestWalletsSummary, + tags=["webserver", "backtest"], +) +def api_get_backtest_wallet(file: str, strategy: str, config=Depends(get_config)): + bt_results_base: Path = config["user_data_dir"] / "backtest_results" + file_abs = (bt_results_base / file).with_suffix(".zip") + # Ensure file is in backtest_results directory + if not is_file_in_dir(file_abs, bt_results_base): + raise HTTPException(status_code=404, detail="File not found.") + + results = get_backtest_wallet_change(file_abs, strategy) + if results is None: + raise HTTPException(status_code=404, detail="File not found.") + # Consolidate the wallet to the base currency + results.loc[:, "total"] = results["price"] * results["balance"] + results = results.groupby(["date", "__date_ts"]).agg({"total": "sum"}).reset_index() + + return { + "columns": results.columns.tolist(), + "data": results.values.tolist(), + "length": len(results), + } diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 7952d5724..df86300e5 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -679,6 +679,12 @@ class BacktestMarketChange(BaseModel): data: list[list[Any]] +class BacktestWalletsSummary(BaseModel): + columns: list[str] + length: int + data: list[list[Any]] + + class MarketRequest(ExchangeModePayloadMixin, BaseModel): base: str | None = None quote: str | None = None diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index d25bda78e..64283898f 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -69,7 +69,8 @@ logger = logging.getLogger(__name__) # 2.45: Add price to forceexit endpoint # 2.46: Add prepend_data to download-data endpoint # 2.47: Add Strategy parameters -API_VERSION = 2.47 +# 2.48: add /backtest/history/wallets endpoint +API_VERSION = 2.48 # Public API, requires no auth. router_public = APIRouter() From ed560f995d751efe12a85e452f4e83a3ebf3d56d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 26 Apr 2025 19:40:06 +0200 Subject: [PATCH 044/315] feat: add WalletBalance model --- freqtrade/persistence/models.py | 2 ++ freqtrade/persistence/wallet_history.py | 28 +++++++++++++++++++++ freqtrade/wallets.py | 33 +++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 freqtrade/persistence/wallet_history.py diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 4d4808eeb..a9c8b8320 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -20,6 +20,7 @@ from freqtrade.persistence.key_value_store import _KeyValueStoreModel from freqtrade.persistence.migrations import check_migrate from freqtrade.persistence.pairlock import PairLock from freqtrade.persistence.trade_model import Order, Trade +from freqtrade.persistence.wallet_history import WalletBalance logger = logging.getLogger(__name__) @@ -91,6 +92,7 @@ def init_db(db_url: str) -> None: _CustomData.session = scoped_session( sessionmaker(bind=engine, autoflush=True), scopefunc=get_request_or_thread_id ) + WalletBalance.session = Trade.session previous_tables = inspect(engine).get_table_names() ModelBase.metadata.create_all(engine) diff --git a/freqtrade/persistence/wallet_history.py b/freqtrade/persistence/wallet_history.py new file mode 100644 index 000000000..aa84a65b3 --- /dev/null +++ b/freqtrade/persistence/wallet_history.py @@ -0,0 +1,28 @@ +from datetime import datetime +from typing import ClassVar + +from sqlalchemy import DateTime, Float, Index, Integer, String, UniqueConstraint +from sqlalchemy.orm import Mapped, mapped_column + +from freqtrade.persistence.base import ModelBase, SessionType +from freqtrade.wallets import Wallets + + +class WalletBalance(ModelBase): + """ + Daily wallet state tracking with minimal fields + """ + + __tablename__ = "wallet_balance" + session: ClassVar[SessionType] + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + timestamp: Mapped[datetime] = mapped_column(DateTime, nullable=False, index=True) + currency: Mapped[str] = mapped_column(String(25), nullable=False) + price: Mapped[float] = mapped_column(Float, nullable=True) + balance: Mapped[float] = mapped_column(Float, nullable=False) + + __table_args__ = ( + # Ensure one record per currency per day + UniqueConstraint("timestamp", "currency", name="unique_wallet_daily"), + ) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 56c32adb6..64fba9656 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -11,6 +11,7 @@ from freqtrade.exceptions import DependencyException from freqtrade.exchange import Exchange from freqtrade.misc import safe_value_fallback from freqtrade.persistence import LocalTrade, Trade +from freqtrade.persistence.wallet_history import WalletBalance from freqtrade.util.datetime_helpers import dt_now @@ -445,3 +446,35 @@ class Wallets: logger.debug(msg) else: logger.info(msg) + + def record_wallet_state(self) -> None: + """ + Record daily wallet totals to database + """ + if self.is_backtest: + # only record in live mode. + return + timestamp = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) + + # Record total balances for all currencies + for wallet in self.get_all_balances().values(): + # TODO: exclude minimal balances + price = self._exchange.get_conversion_rate(wallet.currency, self._stake_currency) + wallet_record = WalletBalance( + timestamp=timestamp, + currency=wallet.currency, + price=price, + balance=wallet.total, + ) + WalletBalance.session.add(wallet_record) + + for position in self.get_all_positions().values(): + price = self._exchange.get_conversion_rate(position.symbol, self._stake_currency) + position_record = WalletBalance( + timestamp=timestamp, + currency=position.pair, + price=position.price, + balance=position.amount, + ) + WalletBalance.session.add(position_record) + WalletBalance.session.commit() From b614ec4ef9476648250124d9665b5aa942f5fff1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 27 Apr 2025 08:26:07 +0200 Subject: [PATCH 045/315] chore: schedule wallet_state capturing every night. --- freqtrade/freqtradebot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 2b4d6c8ff..40db0effe 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -172,6 +172,7 @@ class FreqtradeBot(LoggingMixin): self._schedule.every().day.at(t).do(update) self._schedule.every().day.at("00:02").do(self.exchange.ws_connection_reset) + self._schedule.every().day.at("00:07").do(self.wallets.record_wallet_state) self.strategy.ft_bot_start() # Initialize protections AFTER bot start - otherwise parameters are not loaded. From cba49307b6024d41aa88b6ae2eb18299ac5727b6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 27 Apr 2025 08:29:24 +0200 Subject: [PATCH 046/315] chore: improve imports --- freqtrade/persistence/__init__.py | 1 + freqtrade/persistence/wallet_history.py | 3 +-- freqtrade/wallets.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/freqtrade/persistence/__init__.py b/freqtrade/persistence/__init__.py index 3612544ee..4fa003e02 100644 --- a/freqtrade/persistence/__init__.py +++ b/freqtrade/persistence/__init__.py @@ -10,3 +10,4 @@ from freqtrade.persistence.usedb_context import ( disable_database_use, enable_database_use, ) +from freqtrade.persistence.wallet_history import WalletBalance diff --git a/freqtrade/persistence/wallet_history.py b/freqtrade/persistence/wallet_history.py index aa84a65b3..c89553937 100644 --- a/freqtrade/persistence/wallet_history.py +++ b/freqtrade/persistence/wallet_history.py @@ -1,11 +1,10 @@ from datetime import datetime from typing import ClassVar -from sqlalchemy import DateTime, Float, Index, Integer, String, UniqueConstraint +from sqlalchemy import DateTime, Float, Integer, String, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from freqtrade.persistence.base import ModelBase, SessionType -from freqtrade.wallets import Wallets class WalletBalance(ModelBase): diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 64fba9656..d7d3af4aa 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -10,8 +10,7 @@ from freqtrade.enums import RunMode, TradingMode from freqtrade.exceptions import DependencyException from freqtrade.exchange import Exchange from freqtrade.misc import safe_value_fallback -from freqtrade.persistence import LocalTrade, Trade -from freqtrade.persistence.wallet_history import WalletBalance +from freqtrade.persistence import LocalTrade, Trade, WalletBalance from freqtrade.util.datetime_helpers import dt_now From e52276e3da1a5a0592db9feed59278bacf58a617 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 27 Apr 2025 14:37:41 +0200 Subject: [PATCH 047/315] chore: fix import error --- freqtrade/data/btanalysis/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/data/btanalysis/__init__.py b/freqtrade/data/btanalysis/__init__.py index 2253a45ae..bd8b55df8 100644 --- a/freqtrade/data/btanalysis/__init__.py +++ b/freqtrade/data/btanalysis/__init__.py @@ -7,6 +7,7 @@ from .bt_fileutils import ( get_backtest_market_change, get_backtest_result, get_backtest_resultlist, + get_backtest_wallet_change, get_latest_backtest_filename, get_latest_hyperopt_file, get_latest_hyperopt_filename, From 37fae7ea71ccff887bc680c251182f5b2e099cc1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 27 Apr 2025 15:03:54 +0200 Subject: [PATCH 048/315] feat: use proper properties for record_wallet_state --- freqtrade/wallets.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index d7d3af4aa..295fb1277 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -450,7 +450,7 @@ class Wallets: """ Record daily wallet totals to database """ - if self.is_backtest: + if self._is_backtest: # only record in live mode. return timestamp = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) @@ -471,9 +471,9 @@ class Wallets: price = self._exchange.get_conversion_rate(position.symbol, self._stake_currency) position_record = WalletBalance( timestamp=timestamp, - currency=position.pair, - price=position.price, - balance=position.amount, + currency=position.symbol, + price=price, + balance=position.position, ) WalletBalance.session.add(position_record) WalletBalance.session.commit() From 63869be3760aeb2af5c71c161d4db80bec8a9ec5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 27 Apr 2025 15:30:49 +0200 Subject: [PATCH 049/315] feat: initial attempt at migrating walletHistory --- .../data/btanalysis/trade_parallelism.py | 44 +++++++++ freqtrade/util/migrations/__init__.py | 6 +- .../util/migrations/migrate_wallet_history.py | 99 +++++++++++++++++++ 3 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 freqtrade/util/migrations/migrate_wallet_history.py diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index eabdcf08a..b68692cf9 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -1,9 +1,16 @@ import logging +from datetime import datetime import numpy as np import pandas as pd from freqtrade.constants import IntOrInf +from freqtrade.exchange.exchange_utils_timeframe import ( + timeframe_to_next_date, + timeframe_to_prev_date, + timeframe_to_resample_freq, +) +from freqtrade.util.datetime_helpers import dt_from_ts logger = logging.getLogger(__name__) @@ -58,3 +65,40 @@ def evaluate_result_multi( """ df_final = analyze_trade_parallelism(trades, timeframe) return df_final[df_final["open_trades"] > max_open_trades] + + +def balance_distribution_over_time( + trades: pd.DataFrame, + min_date: datetime, + max_date: datetime, + timeframe: str, + stake_currency: str, + start_balance: float, + pairlist: list[str], +) -> pd.DataFrame: + """ + Return a dataframe with stake_currency and the pairlist as columns + Each column will contain the amount of the currency at the given time + """ + min_date_res = timeframe_to_prev_date(timeframe, min_date) + max_date_res = timeframe_to_next_date(timeframe, max_date) + index = pd.date_range(min_date_res, max_date_res, freq=timeframe_to_resample_freq(timeframe)) + df = pd.DataFrame(index=index) + df[stake_currency] = float(start_balance) + df[pairlist] = 0.0 + for trade in trades.sort_values(by=["open_date"]).itertuples(): + for order in sorted(trade.orders, key=lambda x: x["order_filled_timestamp"]): + filled_at = pd.Timestamp(dt_from_ts(order["order_filled_timestamp"])) + real_amount = order["amount"] / trade.leverage + stake = order["safe_price"] * real_amount + if order["ft_is_entry"]: + fee = stake * trade.fee_open + df.loc[filled_at:, trade.pair] += real_amount + df.loc[filled_at:, stake_currency] -= stake + fee + else: + fee = stake * trade.fee_close + df.loc[filled_at:, trade.pair] -= real_amount + df.loc[filled_at:, stake_currency] += stake - fee + + df = df.round(14) + return df diff --git a/freqtrade/util/migrations/__init__.py b/freqtrade/util/migrations/__init__.py index 20aafb04b..0ed6c97da 100644 --- a/freqtrade/util/migrations/__init__.py +++ b/freqtrade/util/migrations/__init__.py @@ -1,5 +1,6 @@ from freqtrade.exchange import Exchange from freqtrade.util.migrations.funding_rate_mig import migrate_funding_fee_timeframe +from freqtrade.util.migrations.migrate_wallet_history import migrate_wallet_history def migrate_data(config, exchange: Exchange | None = None) -> None: @@ -10,10 +11,9 @@ def migrate_data(config, exchange: Exchange | None = None) -> None: migrate_funding_fee_timeframe(config, exchange) -def migrate_live_content(config, exchange: Exchange | None = None) -> None: +def migrate_live_content(config, exchange: Exchange) -> None: """ Migrate database content from old formats to new formats Used for dry/live mode. """ - # Currently not used - pass + migrate_wallet_history(config, exchange) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py new file mode 100644 index 000000000..ef3596e14 --- /dev/null +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -0,0 +1,99 @@ +import pandas as pd + +from freqtrade.constants import Config +from freqtrade.data.btanalysis.bt_fileutils import trade_list_to_dataframe +from freqtrade.data.btanalysis.trade_parallelism import balance_distribution_over_time +from freqtrade.exchange import Exchange +from freqtrade.exchange.exchange_utils_timeframe import timeframe_to_prev_date +from freqtrade.persistence.key_value_store import KeyValueStore +from freqtrade.persistence.trade_model import Trade +from freqtrade.persistence.wallet_history import WalletBalance +from freqtrade.util.datetime_helpers import dt_now, dt_ts + + +def migrate_wallet_history(config: Config, exchange: Exchange): + if not exchange.get_option("ohlcv_has_history", True): + # we can't fill up wallet history without ohlcv history + return + trade_df = trade_list_to_dataframe(Trade.get_trades_proxy()) + if trade_df.empty: + # no trades, nothing to do + return + starting_balance = 1000 # wallets.get_starting_balance() + pairlist = list(trade_df["pair"].unique()) + timeframe = "1d" + stake_currency = config["stake_currency"] + min_date = timeframe_to_prev_date(timeframe, KeyValueStore.get_datetime_value("bot_start_time")) + balance_dist = balance_distribution_over_time( + trade_df, + min_date=min_date, + max_date=dt_now(), + start_balance=starting_balance, + stake_currency=stake_currency, + timeframe=timeframe, + pairlist=pairlist, + ) + + data = exchange.refresh_latest_ohlcv( + [(p, timeframe, config["candle_type_def"]) for p in pairlist], + since_ms=dt_ts(min_date), + cache=False, + drop_incomplete=False, + ) + + dfs = [] + # Combine all dataframes into one using the open rate + for p, x in data.items(): + x = x.set_index("date", drop=True) + col = f"{p[0]}_open" + x[col] = x["open"] + dfs.append(x[[col]]) + + merged = pd.concat(dfs, axis=1) + + balance_dist = balance_dist.join(merged, how="left") + for p in pairlist: + balance_dist[f"{p}_value"] = balance_dist[f"{p}_open"] * balance_dist[p] + + balance_dist["total_value"] = balance_dist[ + [f"{p}_value" for p in pairlist] + [stake_currency] + ].sum(axis=1) + + # Convert balance_dist to WalletBalance entries + wallet_entries = [] + for date, row in balance_dist.iterrows(): + # Add stake currency entry + if not pd.isna(row[stake_currency]): + wallet_entries.append( + WalletBalance( + timestamp=date, + currency=stake_currency, + price=1.0, # Stake currency price is always 1.0 + balance=row[stake_currency], + ) + ) + + # Add entries for each trading pair + for pair in pairlist: + base_currency = pair.split("/")[0] + # Only add entry if balance is not empty/NaN + if not pd.isna(row[pair]) and row[pair] > 0: + price_col = f"{pair}_open" + price = row[price_col] if not pd.isna(row[price_col]) else None + + wallet_entries.append( + WalletBalance( + timestamp=date, currency=base_currency, price=price, balance=row[pair] + ) + ) + + # Save entries to database + if wallet_entries: + try: + # Use bulk_save_objects for better performance + WalletBalance.session.bulk_save_objects(wallet_entries) + WalletBalance.session.commit() + print(f"Successfully created {len(wallet_entries)} wallet balance records") + except Exception as e: + WalletBalance.session.rollback() + print(f"Error saving wallet balance records: {e}") From 2d2cee2c5809f60ba083487a826aeaac09123d0c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 27 Apr 2025 15:31:21 +0200 Subject: [PATCH 050/315] refactor: rename walletsSummary schema --- freqtrade/rpc/api_server/api_backtest.py | 4 ++-- freqtrade/rpc/api_server/api_schemas.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 92339c7d2..9f037f106 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -30,7 +30,7 @@ from freqtrade.rpc.api_server.api_schemas import ( BacktestMetadataUpdate, BacktestRequest, BacktestResponse, - BacktestWalletsSummary, + WalletsSummary, ) from freqtrade.rpc.api_server.deps import get_config, verify_strategy from freqtrade.rpc.api_server.webserver_bgwork import ApiBG @@ -360,7 +360,7 @@ def api_get_backtest_market_change(file: str, config=Depends(get_config)): @router.get( "/backtest/history/{file}/{strategy}/wallet", - response_model=BacktestWalletsSummary, + response_model=WalletsSummary, tags=["webserver", "backtest"], ) def api_get_backtest_wallet(file: str, strategy: str, config=Depends(get_config)): diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index df86300e5..f84b830a4 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -679,7 +679,7 @@ class BacktestMarketChange(BaseModel): data: list[list[Any]] -class BacktestWalletsSummary(BaseModel): +class WalletsSummary(BaseModel): columns: list[str] length: int data: list[list[Any]] From 281b627db35b49e6e23ba9d20a25f43ef64d20b5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 27 Apr 2025 15:32:28 +0200 Subject: [PATCH 051/315] feat: add historic_balance api endpoint --- freqtrade/rpc/api_server/api_trading.py | 16 ++++++++++++++++ freqtrade/rpc/rpc.py | 15 ++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server/api_trading.py b/freqtrade/rpc/api_server/api_trading.py index 3ec7a08b3..085116d0a 100644 --- a/freqtrade/rpc/api_server/api_trading.py +++ b/freqtrade/rpc/api_server/api_trading.py @@ -31,6 +31,7 @@ from freqtrade.rpc.api_server.api_schemas import ( ResultMsg, Stats, StatusMsg, + WalletsSummary, WhitelistResponse, ) from freqtrade.rpc.api_server.deps import get_config, get_rpc @@ -104,6 +105,21 @@ def stats(rpc: RPC = Depends(get_rpc)): return rpc._rpc_stats() +@router.get( + "/historic_balance", + response_model=WalletsSummary, + tags=["info"], +) +def api_get_backtest_wallet(rpc: RPC = Depends(get_rpc)): + results = rpc._rpc_get_historic_balance() + + return { + "columns": results.columns.tolist(), + "data": results.values.tolist(), + "length": len(results), + } + + @router.get("/daily", response_model=DailyWeeklyMonthly, tags=["Trading-info"]) def daily( timescale: int = Query(7, ge=1, description="Number of days to fetch data for"), diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 37b8dfa6d..7ad480989 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -12,7 +12,7 @@ import psutil from dateutil.relativedelta import relativedelta from dateutil.tz import tzlocal from numpy import inf, int64, isnan, mean, nan -from pandas import DataFrame, NaT +from pandas import DataFrame, NaT, read_sql from sqlalchemy import func, select from freqtrade import __version__ @@ -785,6 +785,19 @@ class RPC: "bot_start_date": format_date(bot_start), } + def _rpc_get_historic_balance(self) -> DataFrame: + """ + Returns the historic balance of the bot + :return: DataFrame with the balance history + """ + results = read_sql("wallet_balance", con=Trade.session.bind, parse_dates=["timestamp"]) + results.loc[:, "total"] = results["price"] * results["balance"] + results = results.rename({"timestamp": "date"}, axis=1) + results.loc[:, "__date_ts"] = results.loc[:, "date"].astype("int64") // 1000 // 1000 + + results = results.groupby(["date", "__date_ts"]).agg({"total": "sum"}).reset_index() + return results + def __balance_get_est_stake( self, coin: str, stake_currency: str, amount: float, balance: Wallet ) -> tuple[float, float]: From 1f15d28eebe89ab5432126332ef05011c0681e1a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 27 Apr 2025 16:35:53 +0200 Subject: [PATCH 052/315] feat: prevent duplicate wallet migrations --- freqtrade/persistence/key_value_store.py | 1 + .../util/migrations/migrate_wallet_history.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/freqtrade/persistence/key_value_store.py b/freqtrade/persistence/key_value_store.py index 310e82b4b..6abc31889 100644 --- a/freqtrade/persistence/key_value_store.py +++ b/freqtrade/persistence/key_value_store.py @@ -22,6 +22,7 @@ KeyStoreKeys = Literal[ "bot_start_time", "startup_time", "binance_migration", + "wallet_history_migration", ] diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index ef3596e14..3f4aa29b8 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -1,3 +1,5 @@ +import logging + import pandas as pd from freqtrade.constants import Config @@ -11,10 +13,22 @@ from freqtrade.persistence.wallet_history import WalletBalance from freqtrade.util.datetime_helpers import dt_now, dt_ts +logger = logging.getLogger(__name__) + + def migrate_wallet_history(config: Config, exchange: Exchange): if not exchange.get_option("ohlcv_has_history", True): # we can't fill up wallet history without ohlcv history return + if KeyValueStore.get_int_value("wallet_history_migration"): + logger.debug("Wallet history migration already completed.") + return + + _migrate_wallet_history(config, exchange) + KeyValueStore.store_value("wallet_history_migration", 1) + + +def _migrate_wallet_history(config: Config, exchange: Exchange): trade_df = trade_list_to_dataframe(Trade.get_trades_proxy()) if trade_df.empty: # no trades, nothing to do From 51cf051fce74cf1bf738405001302dba2d8aec17 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 28 Apr 2025 20:27:01 +0200 Subject: [PATCH 053/315] test: add test for backtest/wallets endpoint --- tests/rpc/test_rpc_apiserver.py | 53 ++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 1247d4c02..226abec1c 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -7,8 +7,10 @@ import logging import time from copy import deepcopy from datetime import UTC, datetime, timedelta +from io import BytesIO from pathlib import Path from unittest.mock import ANY, MagicMock, PropertyMock, patch +from zipfile import ZipFile import pandas as pd import pytest @@ -3275,7 +3277,7 @@ def test_api_patch_backtest_history_entry(botclient, tmp_path: Path): assert fileres[CURRENT_TEST_STRATEGY]["notes"] == "FooBar" -def test_api_patch_backtest_market_change(botclient, tmp_path: Path): +def test_api_backtest_market_change(botclient, tmp_path: Path): ftbot, client = botclient # Create a temporary directory and file @@ -3313,6 +3315,55 @@ def test_api_patch_backtest_market_change(botclient, tmp_path: Path): ] +def test_api_backtest_wallets(botclient, tmp_path: Path): + ftbot, client = botclient + + # Create a temporary directory and file + bt_results_base = tmp_path / "backtest_results" + bt_results_base.mkdir() + zip_file = bt_results_base / "backtest_15.zip" + with ZipFile(zip_file, "w") as zipf: + wallet_df = pd.DataFrame( + { + "date": [ + "2018-01-01T00:00:00Z", + "2018-01-01T00:00:00Z", + "2018-01-01T00:05:00Z", + "2018-01-01T00:05:00Z", + ], + "currency": ["ETH", "BTC", "ETH", "BTC"], + "price": [2000, 60_000, 2001, 60_001], + "balance": [0.5, 0.25, 0.5, 0.25], + } + ) + wallet_df["date"] = pd.to_datetime(wallet_df["date"]) + wallet_buf = BytesIO() + wallet_df.reset_index().to_feather(wallet_buf, compression_level=9, compression="lz4") + wallet_buf.seek(0) + zipf.writestr("backtest_15_SampleStrategy_wallet.feather", wallet_buf.read()) + + # Wrong basedirectory + rc = client_get(client, f"{BASE_URI}/backtest/history/randomFile.json/SampleStrategy/wallet") + assert_response(rc, 503) + + ftbot.config["user_data_dir"] = tmp_path + ftbot.config["runmode"] = RunMode.WEBSERVER + + # Nonexisting file + rc = client_get(client, f"{BASE_URI}/backtest/history/randomFile.json/SampleStrategy/wallet") + assert_response(rc, 404) + + rc = client_get(client, f"{BASE_URI}/backtest/history/backtest_15/SampleStrategy/wallet") + assert_response(rc, 200) + result = rc.json() + assert result["length"] == 2 + assert result["columns"] == ["date", "__date_ts", "total"] + assert result["data"] == [ + ["2018-01-01T00:00:00Z", 1514764800000, 16000.0], + ["2018-01-01T00:05:00Z", 1514765100000, 16000.75], + ] + + def test_health(botclient): _ftbot, client = botclient From c7878130f12272217319434689fb452ba48f797b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 10 May 2025 16:17:44 +0200 Subject: [PATCH 054/315] feat: capture wallet_history_date --- freqtrade/persistence/key_value_store.py | 1 + freqtrade/util/migrations/migrate_wallet_history.py | 1 + 2 files changed, 2 insertions(+) diff --git a/freqtrade/persistence/key_value_store.py b/freqtrade/persistence/key_value_store.py index 6abc31889..ac3cedcd1 100644 --- a/freqtrade/persistence/key_value_store.py +++ b/freqtrade/persistence/key_value_store.py @@ -23,6 +23,7 @@ KeyStoreKeys = Literal[ "startup_time", "binance_migration", "wallet_history_migration", + "wallet_history_migration_date", ] diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 3f4aa29b8..7250d59e6 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -107,6 +107,7 @@ def _migrate_wallet_history(config: Config, exchange: Exchange): # Use bulk_save_objects for better performance WalletBalance.session.bulk_save_objects(wallet_entries) WalletBalance.session.commit() + KeyValueStore.store_value("wallet_history_migration_date", dt_now()) print(f"Successfully created {len(wallet_entries)} wallet balance records") except Exception as e: WalletBalance.session.rollback() From d6a0a4ec6c438cd0b5e6332b2c7bbaffa7b86401 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Jun 2025 06:56:04 +0200 Subject: [PATCH 055/315] chore: rename WalletHistory model to better match it's intend --- freqtrade/persistence/__init__.py | 2 +- freqtrade/persistence/models.py | 4 ++-- freqtrade/persistence/wallet_history.py | 4 ++-- freqtrade/rpc/rpc.py | 2 +- .../util/migrations/migrate_wallet_history.py | 14 +++++++------- freqtrade/wallets.py | 12 ++++++------ 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/freqtrade/persistence/__init__.py b/freqtrade/persistence/__init__.py index 4fa003e02..4966c0b83 100644 --- a/freqtrade/persistence/__init__.py +++ b/freqtrade/persistence/__init__.py @@ -10,4 +10,4 @@ from freqtrade.persistence.usedb_context import ( disable_database_use, enable_database_use, ) -from freqtrade.persistence.wallet_history import WalletBalance +from freqtrade.persistence.wallet_history import WalletHistory diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index a9c8b8320..05905abfe 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -20,7 +20,7 @@ from freqtrade.persistence.key_value_store import _KeyValueStoreModel from freqtrade.persistence.migrations import check_migrate from freqtrade.persistence.pairlock import PairLock from freqtrade.persistence.trade_model import Order, Trade -from freqtrade.persistence.wallet_history import WalletBalance +from freqtrade.persistence.wallet_history import WalletHistory logger = logging.getLogger(__name__) @@ -92,7 +92,7 @@ def init_db(db_url: str) -> None: _CustomData.session = scoped_session( sessionmaker(bind=engine, autoflush=True), scopefunc=get_request_or_thread_id ) - WalletBalance.session = Trade.session + WalletHistory.session = Trade.session previous_tables = inspect(engine).get_table_names() ModelBase.metadata.create_all(engine) diff --git a/freqtrade/persistence/wallet_history.py b/freqtrade/persistence/wallet_history.py index c89553937..8fbd81661 100644 --- a/freqtrade/persistence/wallet_history.py +++ b/freqtrade/persistence/wallet_history.py @@ -7,12 +7,12 @@ from sqlalchemy.orm import Mapped, mapped_column from freqtrade.persistence.base import ModelBase, SessionType -class WalletBalance(ModelBase): +class WalletHistory(ModelBase): """ Daily wallet state tracking with minimal fields """ - __tablename__ = "wallet_balance" + __tablename__ = "wallet_history" session: ClassVar[SessionType] id: Mapped[int] = mapped_column(Integer, primary_key=True) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 7ad480989..22c6ec865 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -790,7 +790,7 @@ class RPC: Returns the historic balance of the bot :return: DataFrame with the balance history """ - results = read_sql("wallet_balance", con=Trade.session.bind, parse_dates=["timestamp"]) + results = read_sql("wallet_history", con=Trade.session.bind, parse_dates=["timestamp"]) results.loc[:, "total"] = results["price"] * results["balance"] results = results.rename({"timestamp": "date"}, axis=1) results.loc[:, "__date_ts"] = results.loc[:, "date"].astype("int64") // 1000 // 1000 diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 7250d59e6..c43d20ef2 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -9,7 +9,7 @@ from freqtrade.exchange import Exchange from freqtrade.exchange.exchange_utils_timeframe import timeframe_to_prev_date from freqtrade.persistence.key_value_store import KeyValueStore from freqtrade.persistence.trade_model import Trade -from freqtrade.persistence.wallet_history import WalletBalance +from freqtrade.persistence.wallet_history import WalletHistory from freqtrade.util.datetime_helpers import dt_now, dt_ts @@ -73,13 +73,13 @@ def _migrate_wallet_history(config: Config, exchange: Exchange): [f"{p}_value" for p in pairlist] + [stake_currency] ].sum(axis=1) - # Convert balance_dist to WalletBalance entries + # Convert balance_dist to WalletHistory entries wallet_entries = [] for date, row in balance_dist.iterrows(): # Add stake currency entry if not pd.isna(row[stake_currency]): wallet_entries.append( - WalletBalance( + WalletHistory( timestamp=date, currency=stake_currency, price=1.0, # Stake currency price is always 1.0 @@ -96,7 +96,7 @@ def _migrate_wallet_history(config: Config, exchange: Exchange): price = row[price_col] if not pd.isna(row[price_col]) else None wallet_entries.append( - WalletBalance( + WalletHistory( timestamp=date, currency=base_currency, price=price, balance=row[pair] ) ) @@ -105,10 +105,10 @@ def _migrate_wallet_history(config: Config, exchange: Exchange): if wallet_entries: try: # Use bulk_save_objects for better performance - WalletBalance.session.bulk_save_objects(wallet_entries) - WalletBalance.session.commit() + WalletHistory.session.bulk_save_objects(wallet_entries) + WalletHistory.session.commit() KeyValueStore.store_value("wallet_history_migration_date", dt_now()) print(f"Successfully created {len(wallet_entries)} wallet balance records") except Exception as e: - WalletBalance.session.rollback() + WalletHistory.session.rollback() print(f"Error saving wallet balance records: {e}") diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 295fb1277..0cb707a84 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -10,7 +10,7 @@ from freqtrade.enums import RunMode, TradingMode from freqtrade.exceptions import DependencyException from freqtrade.exchange import Exchange from freqtrade.misc import safe_value_fallback -from freqtrade.persistence import LocalTrade, Trade, WalletBalance +from freqtrade.persistence import LocalTrade, Trade, WalletHistory from freqtrade.util.datetime_helpers import dt_now @@ -459,21 +459,21 @@ class Wallets: for wallet in self.get_all_balances().values(): # TODO: exclude minimal balances price = self._exchange.get_conversion_rate(wallet.currency, self._stake_currency) - wallet_record = WalletBalance( + wallet_record = WalletHistory( timestamp=timestamp, currency=wallet.currency, price=price, balance=wallet.total, ) - WalletBalance.session.add(wallet_record) + WalletHistory.session.add(wallet_record) for position in self.get_all_positions().values(): price = self._exchange.get_conversion_rate(position.symbol, self._stake_currency) - position_record = WalletBalance( + position_record = WalletHistory( timestamp=timestamp, currency=position.symbol, price=price, balance=position.position, ) - WalletBalance.session.add(position_record) - WalletBalance.session.commit() + WalletHistory.session.add(position_record) + WalletHistory.session.commit() From a8295de2b919b7428f7b54272e35595a4b995338 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Jun 2025 07:05:01 +0200 Subject: [PATCH 056/315] chore: fix endpoint naming --- freqtrade/rpc/api_server/api_trading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server/api_trading.py b/freqtrade/rpc/api_server/api_trading.py index 085116d0a..35f48a616 100644 --- a/freqtrade/rpc/api_server/api_trading.py +++ b/freqtrade/rpc/api_server/api_trading.py @@ -110,7 +110,7 @@ def stats(rpc: RPC = Depends(get_rpc)): response_model=WalletsSummary, tags=["info"], ) -def api_get_backtest_wallet(rpc: RPC = Depends(get_rpc)): +def api_get_wallet_history(rpc: RPC = Depends(get_rpc)): results = rpc._rpc_get_historic_balance() return { From 6c249255220bbd495d15cb4c2fd93236d0f61d4f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Jun 2025 07:21:41 +0200 Subject: [PATCH 057/315] fix: make sure wallet_summary exists --- freqtrade/optimize/backtesting.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 1ac5fa989..e9edb2569 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -1882,7 +1882,11 @@ class Backtesting: dt_appendix, market_change_data=combined_res, analysis_results=self.analysis_results, - wallet_summary={s: x["wallet_summary"] for s, x in self.all_bt_content.items()}, + wallet_summary={ + s: x["wallet_summary"] + for s, x in self.all_bt_content.items() + if "wallet_summary" in x + }, strategy_files={s.get_strategy_name(): s.__file__ for s in self.strategylist}, ) From 15be0510fc24f1d489b240e4a693582059eef8fa Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 15 Jun 2025 07:41:41 +0200 Subject: [PATCH 058/315] feat: return "capture_start_ts" as part of API response --- freqtrade/rpc/api_server/api_schemas.py | 3 +++ freqtrade/rpc/api_server/api_trading.py | 3 ++- freqtrade/rpc/rpc.py | 7 ++++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index f84b830a4..e44e0c612 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -683,6 +683,9 @@ class WalletsSummary(BaseModel): columns: list[str] length: int data: list[list[Any]] + # start date of the effectively captured data + # Before this date, it's based on a reconstructed wallet history + capture_start_ts: int | None = None class MarketRequest(ExchangeModePayloadMixin, BaseModel): diff --git a/freqtrade/rpc/api_server/api_trading.py b/freqtrade/rpc/api_server/api_trading.py index 35f48a616..5f61bd91a 100644 --- a/freqtrade/rpc/api_server/api_trading.py +++ b/freqtrade/rpc/api_server/api_trading.py @@ -111,12 +111,13 @@ def stats(rpc: RPC = Depends(get_rpc)): tags=["info"], ) def api_get_wallet_history(rpc: RPC = Depends(get_rpc)): - results = rpc._rpc_get_historic_balance() + results, capture_date_ts = rpc._rpc_get_historic_balance() return { "columns": results.columns.tolist(), "data": results.values.tolist(), "length": len(results), + "capture_start_ts": capture_date_ts, } diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 22c6ec865..147861dca 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -785,10 +785,10 @@ class RPC: "bot_start_date": format_date(bot_start), } - def _rpc_get_historic_balance(self) -> DataFrame: + def _rpc_get_historic_balance(self) -> tuple[DataFrame, int]: """ Returns the historic balance of the bot - :return: DataFrame with the balance history + :return: DataFrame with the balance history and the timestamp of the migration """ results = read_sql("wallet_history", con=Trade.session.bind, parse_dates=["timestamp"]) results.loc[:, "total"] = results["price"] * results["balance"] @@ -796,7 +796,8 @@ class RPC: results.loc[:, "__date_ts"] = results.loc[:, "date"].astype("int64") // 1000 // 1000 results = results.groupby(["date", "__date_ts"]).agg({"total": "sum"}).reset_index() - return results + hist = KeyValueStore.get_datetime_value("wallet_history_migration_date", None) + return results, dt_ts_def(hist, 0) def __balance_get_est_stake( self, coin: str, stake_currency: str, amount: float, balance: Wallet From d105770ff6f5056252636848053db39d30bbfaed Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 17 Jun 2025 06:54:35 +0200 Subject: [PATCH 059/315] chore: Improve response model naming --- freqtrade/rpc/api_server/api_backtest.py | 4 ++-- freqtrade/rpc/api_server/api_schemas.py | 2 +- freqtrade/rpc/api_server/api_trading.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 9f037f106..db01a8d2d 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -30,7 +30,7 @@ from freqtrade.rpc.api_server.api_schemas import ( BacktestMetadataUpdate, BacktestRequest, BacktestResponse, - WalletsSummary, + WalletHistory, ) from freqtrade.rpc.api_server.deps import get_config, verify_strategy from freqtrade.rpc.api_server.webserver_bgwork import ApiBG @@ -360,7 +360,7 @@ def api_get_backtest_market_change(file: str, config=Depends(get_config)): @router.get( "/backtest/history/{file}/{strategy}/wallet", - response_model=WalletsSummary, + response_model=WalletHistory, tags=["webserver", "backtest"], ) def api_get_backtest_wallet(file: str, strategy: str, config=Depends(get_config)): diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index e44e0c612..3837afb43 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -679,7 +679,7 @@ class BacktestMarketChange(BaseModel): data: list[list[Any]] -class WalletsSummary(BaseModel): +class WalletHistory(BaseModel): columns: list[str] length: int data: list[list[Any]] diff --git a/freqtrade/rpc/api_server/api_trading.py b/freqtrade/rpc/api_server/api_trading.py index 5f61bd91a..368de9f79 100644 --- a/freqtrade/rpc/api_server/api_trading.py +++ b/freqtrade/rpc/api_server/api_trading.py @@ -31,7 +31,7 @@ from freqtrade.rpc.api_server.api_schemas import ( ResultMsg, Stats, StatusMsg, - WalletsSummary, + WalletHistory, WhitelistResponse, ) from freqtrade.rpc.api_server.deps import get_config, get_rpc @@ -107,7 +107,7 @@ def stats(rpc: RPC = Depends(get_rpc)): @router.get( "/historic_balance", - response_model=WalletsSummary, + response_model=WalletHistory, tags=["info"], ) def api_get_wallet_history(rpc: RPC = Depends(get_rpc)): From 1f32dbef9a48b4f6b47c16738720afbaa3fb1652 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 17 Jun 2025 06:54:54 +0200 Subject: [PATCH 060/315] feat: enable wallet-capture in webserver mode --- freqtrade/rpc/api_server/api_backtest.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index db01a8d2d..ae50db978 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -20,7 +20,7 @@ from freqtrade.data.btanalysis import ( load_and_merge_backtest_result, update_backtest_metadata, ) -from freqtrade.enums import BacktestState +from freqtrade.enums import BacktestState, RunMode from freqtrade.exceptions import ConfigurationError, DependencyException, OperationalException from freqtrade.ft_types import get_BacktestResultType_default from freqtrade.misc import deep_merge_dicts, is_file_in_dir @@ -108,6 +108,11 @@ def __run_backtest_bg(btconfig: Config): ApiBG.bt["bt"].results, datetime.now().strftime("%Y-%m-%d_%H-%M-%S"), market_change_data=combined_res, + wallet_summary={ + s: x["wallet_summary"] + for s, x in ApiBG.bt["bt"].all_bt_content.items() + if "wallet_summary" in x + }, strategy_files={ s.get_strategy_name(): s.__file__ for s in ApiBG.bt["bt"].strategylist }, @@ -139,6 +144,7 @@ async def api_start_backtest( verify_strategy(bt_settings.strategy) btconfig = deepcopy(config) + btconfig["runmode"] = RunMode.BACKTEST remove_exchange_credentials(btconfig["exchange"], True) settings = dict(bt_settings) if settings.get("freqai", None) is not None: From 235c46ae125a3737821bc50dd550077f0008543d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Jun 2025 06:49:44 +0200 Subject: [PATCH 061/315] chore: add wallet migration timing log --- freqtrade/util/migrations/migrate_wallet_history.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index c43d20ef2..781517c26 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -23,8 +23,9 @@ def migrate_wallet_history(config: Config, exchange: Exchange): if KeyValueStore.get_int_value("wallet_history_migration"): logger.debug("Wallet history migration already completed.") return - + logger.info("Starting wallet history migration...") _migrate_wallet_history(config, exchange) + logger.info("Wallet history migration completed.") KeyValueStore.store_value("wallet_history_migration", 1) From 8a284060d1db94469d8e72381e8a6d4d8b43b58a Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Jun 2025 15:49:14 +0200 Subject: [PATCH 062/315] fix: wrong usage of getdatetimevalue --- freqtrade/rpc/rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 147861dca..f9939c604 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -796,7 +796,7 @@ class RPC: results.loc[:, "__date_ts"] = results.loc[:, "date"].astype("int64") // 1000 // 1000 results = results.groupby(["date", "__date_ts"]).agg({"total": "sum"}).reset_index() - hist = KeyValueStore.get_datetime_value("wallet_history_migration_date", None) + hist = KeyValueStore.get_datetime_value("wallet_history_migration_date") return results, dt_ts_def(hist, 0) def __balance_get_est_stake( From 418acb7034c74dc01fa6d4d2d2772ae805a8f8ba Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Jun 2025 15:50:19 +0200 Subject: [PATCH 063/315] chore: exclude open orders from balance calculation --- freqtrade/data/btanalysis/trade_parallelism.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index b68692cf9..b8f119786 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -87,7 +87,9 @@ def balance_distribution_over_time( df[stake_currency] = float(start_balance) df[pairlist] = 0.0 for trade in trades.sort_values(by=["open_date"]).itertuples(): - for order in sorted(trade.orders, key=lambda x: x["order_filled_timestamp"]): + # Exclude open orders - these won't have order_filled_timestamp set. + orders = [o for o in trade.orders if o["order_filled_timestamp"]] + for order in sorted(orders, key=lambda x: x["order_filled_timestamp"]): filled_at = pd.Timestamp(dt_from_ts(order["order_filled_timestamp"])) real_amount = order["amount"] / trade.leverage stake = order["safe_price"] * real_amount From db2309dfd2be0284ee99e75ceaaa2cd97c502263 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Jun 2025 15:50:39 +0200 Subject: [PATCH 064/315] fix: avoid errors for delisted pairs --- freqtrade/util/migrations/migrate_wallet_history.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 781517c26..ef1094920 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -48,9 +48,10 @@ def _migrate_wallet_history(config: Config, exchange: Exchange): timeframe=timeframe, pairlist=pairlist, ) + pairlist_valid = [p for p in pairlist if p in exchange.markets] data = exchange.refresh_latest_ohlcv( - [(p, timeframe, config["candle_type_def"]) for p in pairlist], + [(p, timeframe, config["candle_type_def"]) for p in pairlist_valid], since_ms=dt_ts(min_date), cache=False, drop_incomplete=False, @@ -67,11 +68,11 @@ def _migrate_wallet_history(config: Config, exchange: Exchange): merged = pd.concat(dfs, axis=1) balance_dist = balance_dist.join(merged, how="left") - for p in pairlist: + for p in pairlist_valid: balance_dist[f"{p}_value"] = balance_dist[f"{p}_open"] * balance_dist[p] balance_dist["total_value"] = balance_dist[ - [f"{p}_value" for p in pairlist] + [stake_currency] + [f"{p}_value" for p in pairlist_valid] + [stake_currency] ].sum(axis=1) # Convert balance_dist to WalletHistory entries @@ -89,7 +90,7 @@ def _migrate_wallet_history(config: Config, exchange: Exchange): ) # Add entries for each trading pair - for pair in pairlist: + for pair in pairlist_valid: base_currency = pair.split("/")[0] # Only add entry if balance is not empty/NaN if not pd.isna(row[pair]) and row[pair] > 0: From b5f31bf82d1f1f537310785a7c4980c36b6324aa Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 20 Jun 2025 16:05:03 +0200 Subject: [PATCH 065/315] chore: use proper starting balance --- freqtrade/util/migrations/migrate_wallet_history.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index ef1094920..86db525ac 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -34,6 +34,7 @@ def _migrate_wallet_history(config: Config, exchange: Exchange): if trade_df.empty: # no trades, nothing to do return + # TODO: use a proper starting balance. starting_balance = 1000 # wallets.get_starting_balance() pairlist = list(trade_df["pair"].unique()) timeframe = "1d" From 9ddabbd849490851be35e282e5fb85a8bdf3a3c4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 14 Sep 2025 11:42:16 +0200 Subject: [PATCH 066/315] feat: use proper starting balance --- freqtrade/freqtradebot.py | 2 +- freqtrade/util/migrations/__init__.py | 7 ++++--- freqtrade/util/migrations/migrate_wallet_history.py | 8 +++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 40db0effe..1646cfa9b 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -237,7 +237,7 @@ class FreqtradeBot(LoggingMixin): Called on startup and after reloading the bot - triggers notifications and performs startup tasks """ - migrate_live_content(self.config, self.exchange) + migrate_live_content(self.config, self.exchange, self.wallets.get_starting_balance()) set_startup_time() self.rpc.startup_messages(self.config, self.pairlists, self.protections) diff --git a/freqtrade/util/migrations/__init__.py b/freqtrade/util/migrations/__init__.py index 0ed6c97da..90f866075 100644 --- a/freqtrade/util/migrations/__init__.py +++ b/freqtrade/util/migrations/__init__.py @@ -1,9 +1,10 @@ +from freqtrade.constants import Config from freqtrade.exchange import Exchange from freqtrade.util.migrations.funding_rate_mig import migrate_funding_fee_timeframe from freqtrade.util.migrations.migrate_wallet_history import migrate_wallet_history -def migrate_data(config, exchange: Exchange | None = None) -> None: +def migrate_data(config: Config, exchange: Exchange | None = None) -> None: """ Migrate persisted data from old formats to new formats """ @@ -11,9 +12,9 @@ def migrate_data(config, exchange: Exchange | None = None) -> None: migrate_funding_fee_timeframe(config, exchange) -def migrate_live_content(config, exchange: Exchange) -> None: +def migrate_live_content(config: Config, exchange: Exchange, starting_balance: float) -> None: """ Migrate database content from old formats to new formats Used for dry/live mode. """ - migrate_wallet_history(config, exchange) + migrate_wallet_history(config, exchange, starting_balance) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 86db525ac..316e74ab1 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -16,7 +16,7 @@ from freqtrade.util.datetime_helpers import dt_now, dt_ts logger = logging.getLogger(__name__) -def migrate_wallet_history(config: Config, exchange: Exchange): +def migrate_wallet_history(config: Config, exchange: Exchange, starting_balance: float): if not exchange.get_option("ohlcv_has_history", True): # we can't fill up wallet history without ohlcv history return @@ -24,18 +24,16 @@ def migrate_wallet_history(config: Config, exchange: Exchange): logger.debug("Wallet history migration already completed.") return logger.info("Starting wallet history migration...") - _migrate_wallet_history(config, exchange) + _migrate_wallet_history(config, exchange, starting_balance) logger.info("Wallet history migration completed.") KeyValueStore.store_value("wallet_history_migration", 1) -def _migrate_wallet_history(config: Config, exchange: Exchange): +def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance: float): trade_df = trade_list_to_dataframe(Trade.get_trades_proxy()) if trade_df.empty: # no trades, nothing to do return - # TODO: use a proper starting balance. - starting_balance = 1000 # wallets.get_starting_balance() pairlist = list(trade_df["pair"].unique()) timeframe = "1d" stake_currency = config["stake_currency"] From ba1092b72659bcb4bfa123362b9893009440624a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 14 Sep 2025 12:05:25 +0200 Subject: [PATCH 067/315] chore: use builtin helpers --- freqtrade/wallets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 0cb707a84..42187eac9 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -11,7 +11,7 @@ from freqtrade.exceptions import DependencyException from freqtrade.exchange import Exchange from freqtrade.misc import safe_value_fallback from freqtrade.persistence import LocalTrade, Trade, WalletHistory -from freqtrade.util.datetime_helpers import dt_now +from freqtrade.util.datetime_helpers import dt_floor_day, dt_now logger = logging.getLogger(__name__) @@ -453,7 +453,7 @@ class Wallets: if self._is_backtest: # only record in live mode. return - timestamp = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) + timestamp = dt_floor_day(datetime.now()) # Record total balances for all currencies for wallet in self.get_all_balances().values(): From 680aeb89c31d7ef3628ad6474bbcf9c7b745ed75 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 16 Nov 2025 10:11:06 +0100 Subject: [PATCH 068/315] feat: store wallet stats --- .../optimize_reports/optimize_reports.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index 2f640bcbf..53a40b519 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -43,6 +43,31 @@ def convert_bt_wallet_collection(wallet_captures: list[tuple]) -> DataFrame: ) +def generate_wallet_stats(wallet_df: DataFrame, stake_currency: str) -> dict[str, Any]: + """Generate wallet statistics from the wallet DataFrame.""" + if wallet_df is None or wallet_df.empty: + return {} + wallet_df.loc[:, "total"] = wallet_df["price"] * wallet_df["balance"] + # Group by date to get total wallet value at each timestamp + wallet = wallet_df.groupby("date")["total"].sum().reset_index() + start_balance = wallet.iloc[0]["total"] + end_balance = wallet.iloc[-1]["total"] + high_balance = wallet["total"].max() + low_balance = wallet["total"].min() + low_date = wallet.iloc[wallet["total"].idxmin()]["date"] + high_date = wallet.iloc[wallet["total"].idxmax()]["date"] + return { + "start_balance": start_balance, + "end_balance": end_balance, + "high_balance": high_balance, + "low_balance": low_balance, + "low_date": low_date.strftime(DATETIME_PRINT_FORMAT), + "low_ts": int(low_date.timestamp() * 1000), + "high_date": high_date.strftime(DATETIME_PRINT_FORMAT), + "high_ts": int(high_date.timestamp() * 1000), + } + + def generate_trade_signal_candles( preprocessed_df: dict[str, DataFrame], bt_results: BacktestContentType, date_col: str ) -> dict[str, DataFrame]: @@ -606,6 +631,7 @@ def generate_strategy_stats( "sharpe": calculate_sharpe(results, min_date, max_date, start_balance), "calmar": calculate_calmar(results, min_date, max_date, start_balance), "sqn": calculate_sqn(results, start_balance), + "wallet_stats": generate_wallet_stats(content.get("wallet_summary"), stake_currency), "profit_factor": profit_factor, "backtest_start": min_date.strftime(DATETIME_PRINT_FORMAT), "backtest_start_ts": int(min_date.timestamp() * 1000), From 1ede18648433ab786ee845664437b8726b361ef7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 16 Nov 2025 10:11:56 +0100 Subject: [PATCH 069/315] feat: display min/max balance --- .../optimize/optimize_reports/bt_output.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index 338fe5ca5..026052ae6 100644 --- a/freqtrade/optimize/optimize_reports/bt_output.py +++ b/freqtrade/optimize/optimize_reports/bt_output.py @@ -288,6 +288,24 @@ def text_table_add_metrics(strat_results: dict) -> None: else [] ) + if wallet_stats := strat_results.get("wallet_stats"): + wallet_metrics = ( + ( + "Min/Max balance realized", + f"{fmt_coin(strat_results['csum_min'], stake)} / " + f"{fmt_coin(strat_results['csum_max'], stake)}", + ), + ( + "Min/Max balance unrealized", + f"{fmt_coin(wallet_stats['low_balance'], stake)} / " + f"{fmt_coin(wallet_stats['high_balance'], stake)}", + ), + ( + "Min/Max balance dates", + f"{wallet_stats['low_date']} / {wallet_stats['high_date']}", + ), + ) + # 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. @@ -408,8 +426,7 @@ def text_table_add_metrics(strat_results: dict) -> None: ), *entry_adjustment_metrics, ("", ""), # Empty line to improve readability - ("Min balance", fmt_coin(strat_results["csum_min"], stake)), - ("Max balance", fmt_coin(strat_results["csum_max"], stake)), + *wallet_metrics, *drawdown_metrics, ("Market change", f"{strat_results['market_change']:.2%}"), ] From c66adf2bf1399aefac9de6b23c5ba67d11d7e0e5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 16 Nov 2025 10:27:34 +0100 Subject: [PATCH 070/315] docs: update backtesting docs with new output --- docs/backtesting.md | 210 +++++++++++++++++++++++--------------------- 1 file changed, 109 insertions(+), 101 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 6b86d9635..5f42a6bcd 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -211,58 +211,59 @@ A backtesting result will look like that: │ TOTAL │ │ 77 │ 0.22 │ 54.774 │ 5.48 │ 22:12:00 │ 67 0 10 87.0 │ └───────────┴─────────────┴────────┴──────────────┴─────────────────┴──────────────┴─────────────────┴────────────────────────┘ SUMMARY METRICS -┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -┃ Metric ┃ Value ┃ -┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ -│ Backtesting from │ 2025-07-01 00:00:00 │ -│ Backtesting to │ 2025-08-01 00:00:00 │ -│ Trading Mode │ Isolated Futures │ -│ Max open trades │ 3 │ -│ │ │ -│ Total/Daily Avg Trades │ 77 / 2.48 │ -│ Starting balance │ 1000 USDT │ -│ Final balance │ 1054.774 USDT │ -│ Absolute profit │ 54.774 USDT │ -│ Total profit % │ 5.48% │ -│ CAGR % │ 87.36% │ -│ Sortino │ 2.48 │ -│ Sharpe │ 3.75 │ -│ Calmar │ 40.99 │ -│ SQN │ 0.69 │ -│ Profit factor │ 1.29 │ -│ Expectancy (Ratio) │ 0.71 (0.04) │ -│ Avg. daily profit │ 1.767 USDT │ -│ Avg. stake amount │ 345.016 USDT │ -│ Total trade volume │ 53316.954 USDT │ -│ │ │ -│ Long / Short trades │ 67 / 10 │ -│ Long / Short profit % │ 8.94% / -3.47% │ -│ Long / Short profit USDT │ 89.425 / -34.651 │ -│ │ │ -│ Best Pair │ LTC/USDT:USDT 5.62% │ -│ Worst Pair │ ADA/USDT:USDT -5.21% │ -│ Best trade │ ETC/USDT:USDT 2.00% │ -│ Worst trade │ ADA/USDT:USDT -10.17% │ -│ Best day │ 26.91 USDT │ -│ Worst day │ -47.741 USDT │ -│ Days win/draw/lose │ 20 / 6 / 5 │ -│ Min/Max/Avg. Duration Winners │ 0d 00:35 / 5d 18:15 / 0d 15:49 │ -│ Min/Max/Avg. Duration Losers │ 0d 10:40 / 17d 08:00 / 2d 17:00 │ -│ Max Consecutive Wins / Loss │ 36 / 3 │ -│ Rejected Entry signals │ 258 │ -│ Entry/Exit Timeouts │ 0 / 0 │ -│ │ │ -│ Min balance │ 1003.168 USDT │ -│ Max balance │ 1149.421 USDT │ -│ Max % of account underwater │ 8.23% │ -│ Absolute drawdown │ 94.647 USDT (8.23%) │ -│ Drawdown duration │ 9 days 08:50:00 │ -│ Profit at drawdown start │ 149.421 USDT │ -│ Profit at drawdown end │ 54.774 USDT │ -│ Drawdown start │ 2025-07-22 15:10:00 │ -│ Drawdown end │ 2025-08-01 00:00:00 │ -│ Market change │ 30.51% │ -└───────────────────────────────┴─────────────────────────────────┘ +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Metric ┃ Value ┃ +┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ Backtesting from │ 2025-07-01 00:00:00 │ +│ Backtesting to │ 2025-08-01 00:00:00 │ +│ Trading Mode │ Isolated Futures │ +│ Max open trades │ 3 │ +│ │ │ +│ Total/Daily Avg Trades │ 77 / 2.48 │ +│ Starting balance │ 1000 USDT │ +│ Final balance │ 1054.669 USDT │ +│ Absolute profit │ 54.669 USDT │ +│ Total profit % │ 5.47% │ +│ CAGR % │ 87.14% │ +│ Sortino │ 2.46 │ +│ Sharpe │ 3.73 │ +│ Calmar │ 40.81 │ +│ SQN │ 0.69 │ +│ Profit factor │ 1.29 │ +│ Expectancy (Ratio) │ 0.71 (0.04) │ +│ Avg. daily profit │ 1.764 USDT │ +│ Avg. stake amount │ 345.251 USDT │ +│ Total trade volume │ 53352.96 USDT │ +│ │ │ +│ Long / Short trades │ 67 / 10 │ +│ Long / Short profit % │ 8.93% / -3.46% │ +│ Long / Short profit USDT │ 89.262 / -34.593 │ +│ │ │ +│ Best Pair │ LTC/USDT:USDT 5.62% │ +│ Worst Pair │ ADA/USDT:USDT -5.21% │ +│ Best trade │ ETC/USDT:USDT 2.00% │ +│ Worst trade │ ADA/USDT:USDT -10.17% │ +│ Best day │ 26.931 USDT │ +│ Worst day │ -47.741 USDT │ +│ Days win/draw/lose │ 20 / 6 / 5 │ +│ Min/Max/Avg. Duration Winners │ 0d 00:35 / 5d 18:15 / 0d 15:49 │ +│ Min/Max/Avg. Duration Losers │ 0d 10:40 / 17d 08:00 / 2d 17:00 │ +│ Max Consecutive Wins / Loss │ 36 / 3 │ +│ Rejected Entry signals │ 258 │ +│ Entry/Exit Timeouts │ 0 / 0 │ +│ │ │ +│ Min/Max balance realized │ 1003.168 USDT / 1149.577 USDT │ +│ Min/Max balance unrealized │ 1000 USDT / 1149.577 USDT │ +│ Min/Max balance dates │ 2025-07-01 00:05:00 / 2025-07-22 15:15:00 │ +│ Max % of account underwater │ 8.26% │ +│ Absolute drawdown │ 94.908 USDT (8.26%) │ +│ Drawdown duration │ 9 days 08:50:00 │ +│ Profit at drawdown start │ 149.577 USDT │ +│ Profit at drawdown end │ 54.669 USDT │ +│ Drawdown start │ 2025-07-22 15:10:00 │ +│ Drawdown end │ 2025-08-01 00:00:00 │ +│ Market change │ 30.51% │ +└───────────────────────────────┴───────────────────────────────────────────┘ Backtested 2025-07-01 00:00:00 -> 2025-08-01 00:00:00 | Max open trades : 3 STRATEGY SUMMARY @@ -329,54 +330,59 @@ The last element of the backtest report is the summary metrics table. It contains key metrics about the performance of your strategy on backtesting data. ``` -┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -┃ Metric ┃ Value ┃ -┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ -│ Backtesting from │ 2025-07-01 00:00:00 │ -│ Backtesting to │ 2025-08-01 00:00:00 │ -│ Trading Mode │ Isolated Futures │ -│ Max open trades │ 3 │ -│ │ │ -│ Total/Daily Avg Trades │ 72 / 2.32 │ -│ Starting balance │ 1000 USDT │ -│ Final balance │ 1106.734 USDT │ -│ Absolute profit │ 106.734 USDT │ -│ Total profit % │ 10.67% │ -│ CAGR % │ 230.04% │ -│ Sortino │ 4.99 │ -│ Sharpe │ 8.00 │ -│ Calmar │ 77.76 │ -│ SQN │ 1.52 │ -│ Profit factor │ 1.79 │ -│ Expectancy (Ratio) │ 1.48 (0.07) │ -│ Avg. daily profit │ 3.443 USDT │ -│ Avg. stake amount │ 363.133 USDT │ -│ Total trade volume │ 52466.174 USDT │ -│ │ │ -│ Best Pair │ LTC/USDT:USDT 4.48% │ -│ Worst Pair │ ADA/USDT:USDT -1.78% │ -│ Best trade │ ETC/USDT:USDT 2.00% │ -│ Worst trade │ ADA/USDT:USDT -10.17% │ -│ Best day │ 23.535 USDT │ -│ Worst day │ -49.813 USDT │ -│ Days win/draw/lose │ 21 / 6 / 4 │ -│ Min/Max/Avg. Duration Winners │ 0d 00:35 / 5d 18:15 / 0d 15:30 │ -│ Min/Max/Avg. Duration Losers │ 0d 12:00 / 17d 08:00 / 3d 23:28 │ -│ Max Consecutive Wins / Loss │ 58 / 4 │ -│ Rejected Entry signals │ 254 │ -│ Entry/Exit Timeouts │ 0 / 0 │ -│ │ │ -│ Min balance │ 1003.168 USDT │ -│ Max balance │ 1209 USDT │ -│ Max % of account underwater │ 8.46% │ -│ Absolute drawdown │ 102.266 USDT (8.46%) │ -│ Drawdown duration │ 9 days 08:50:00 │ -│ Profit at drawdown start │ 209 USDT │ -│ Profit at drawdown end │ 106.734 USDT │ -│ Drawdown start │ 2025-07-22 15:10:00 │ -│ Drawdown end │ 2025-08-01 00:00:00 │ -│ Market change │ 30.51% │ -└───────────────────────────────┴─────────────────────────────────┘ +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Metric ┃ Value ┃ +┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ Backtesting from │ 2025-07-01 00:00:00 │ +│ Backtesting to │ 2025-08-01 00:00:00 │ +│ Trading Mode │ Isolated Futures │ +│ Max open trades │ 3 │ +│ │ │ +│ Total/Daily Avg Trades │ 77 / 2.48 │ +│ Starting balance │ 1000 USDT │ +│ Final balance │ 1054.669 USDT │ +│ Absolute profit │ 54.669 USDT │ +│ Total profit % │ 5.47% │ +│ CAGR % │ 87.14% │ +│ Sortino │ 2.46 │ +│ Sharpe │ 3.73 │ +│ Calmar │ 40.81 │ +│ SQN │ 0.69 │ +│ Profit factor │ 1.29 │ +│ Expectancy (Ratio) │ 0.71 (0.04) │ +│ Avg. daily profit │ 1.764 USDT │ +│ Avg. stake amount │ 345.251 USDT │ +│ Total trade volume │ 53352.96 USDT │ +│ │ │ +│ Long / Short trades │ 67 / 10 │ +│ Long / Short profit % │ 8.93% / -3.46% │ +│ Long / Short profit USDT │ 89.262 / -34.593 │ +│ │ │ +│ Best Pair │ LTC/USDT:USDT 5.62% │ +│ Worst Pair │ ADA/USDT:USDT -5.21% │ +│ Best trade │ ETC/USDT:USDT 2.00% │ +│ Worst trade │ ADA/USDT:USDT -10.17% │ +│ Best day │ 26.931 USDT │ +│ Worst day │ -47.741 USDT │ +│ Days win/draw/lose │ 20 / 6 / 5 │ +│ Min/Max/Avg. Duration Winners │ 0d 00:35 / 5d 18:15 / 0d 15:49 │ +│ Min/Max/Avg. Duration Losers │ 0d 10:40 / 17d 08:00 / 2d 17:00 │ +│ Max Consecutive Wins / Loss │ 36 / 3 │ +│ Rejected Entry signals │ 258 │ +│ Entry/Exit Timeouts │ 0 / 0 │ +│ │ │ +│ Min/Max balance realized │ 1003.168 USDT / 1149.577 USDT │ +│ Min/Max balance unrealized │ 1000 USDT / 1149.577 USDT │ +│ Min/Max balance dates │ 2025-07-01 00:05:00 / 2025-07-22 15:15:00 │ +│ Max % of account underwater │ 8.26% │ +│ Absolute drawdown │ 94.908 USDT (8.26%) │ +│ Drawdown duration │ 9 days 08:50:00 │ +│ Profit at drawdown start │ 149.577 USDT │ +│ Profit at drawdown end │ 54.669 USDT │ +│ Drawdown start │ 2025-07-22 15:10:00 │ +│ Drawdown end │ 2025-08-01 00:00:00 │ +│ Market change │ 30.51% │ +└───────────────────────────────┴───────────────────────────────────────────┘ ``` - `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option). @@ -409,7 +415,9 @@ It contains key metrics about the performance of your strategy on backtesting da - `Max Consecutive Wins / Loss`: Maximum consecutive wins/losses in a row. - `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. +- `Min/Max balance realized`: Lowest and Highest Wallet balance during the backtest period based on closed trades trades. +- `Min/Max balance unrealized`: Lowest and Highest Wallet balance during the backtest period - including capital tied in open trades. +- `Min/Max balance dates`: Dates when the minimum and maximum balance occurred. - `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`: Maximum absolute drawdown experienced, including percentage relative to the account calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.. - `Drawdown duration`: Duration of the largest drawdown period. From c877d267c72b8d3390e6fddce92df89c7243fb17 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 16 Nov 2025 14:17:10 +0100 Subject: [PATCH 071/315] feat: expose minfied when converting trade list to dataframe --- freqtrade/data/btanalysis/bt_fileutils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/btanalysis/bt_fileutils.py b/freqtrade/data/btanalysis/bt_fileutils.py index 7d29ab9a2..103abae5b 100644 --- a/freqtrade/data/btanalysis/bt_fileutils.py +++ b/freqtrade/data/btanalysis/bt_fileutils.py @@ -517,13 +517,16 @@ def load_backtest_analysis_data( return None -def trade_list_to_dataframe(trades: list[Trade] | list[LocalTrade]) -> pd.DataFrame: +def trade_list_to_dataframe( + trades: list[Trade] | list[LocalTrade], *, minified: bool = True +) -> pd.DataFrame: """ Convert list of Trade objects to pandas Dataframe :param trades: List of trade objects + :param minified: Whether to use minified version of trade JSON :return: Dataframe with BT_DATA_COLUMNS """ - df = pd.DataFrame.from_records([t.to_json(True) for t in trades], columns=BT_DATA_COLUMNS) + df = pd.DataFrame.from_records([t.to_json(minified) for t in trades], columns=BT_DATA_COLUMNS) if len(df) > 0: df["close_date"] = pd.to_datetime(df["close_timestamp"], unit="ms", utc=True) df["open_date"] = pd.to_datetime(df["open_timestamp"], unit="ms", utc=True) From bf5ec9891811a66d17dd2951b2f3f7bdf9097567 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 16 Nov 2025 14:17:34 +0100 Subject: [PATCH 072/315] chore: use "filled" over amount for balance distribution --- freqtrade/data/btanalysis/trade_parallelism.py | 2 +- freqtrade/util/migrations/migrate_wallet_history.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index b8f119786..0337ef09a 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -91,7 +91,7 @@ def balance_distribution_over_time( orders = [o for o in trade.orders if o["order_filled_timestamp"]] for order in sorted(orders, key=lambda x: x["order_filled_timestamp"]): filled_at = pd.Timestamp(dt_from_ts(order["order_filled_timestamp"])) - real_amount = order["amount"] / trade.leverage + real_amount = order.get("filled", order["amount"]) / trade.leverage stake = order["safe_price"] * real_amount if order["ft_is_entry"]: fee = stake * trade.fee_open diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 316e74ab1..ec6095aee 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -30,7 +30,7 @@ def migrate_wallet_history(config: Config, exchange: Exchange, starting_balance: def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance: float): - trade_df = trade_list_to_dataframe(Trade.get_trades_proxy()) + trade_df = trade_list_to_dataframe(Trade.get_trades_proxy(), minified=False) if trade_df.empty: # no trades, nothing to do return From 921cb4dad8d8bacc6ca48b2a231a5a02c3cb3420 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 16 Nov 2025 16:21:40 +0100 Subject: [PATCH 073/315] feat: Only ffill until the end of the trade --- freqtrade/data/btanalysis/trade_parallelism.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index 0337ef09a..70b1ea7da 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -87,6 +87,7 @@ def balance_distribution_over_time( df[stake_currency] = float(start_balance) df[pairlist] = 0.0 for trade in trades.sort_values(by=["open_date"]).itertuples(): + end_date = trade.close_date if trade.close_date is not pd.NaT else None # Exclude open orders - these won't have order_filled_timestamp set. orders = [o for o in trade.orders if o["order_filled_timestamp"]] for order in sorted(orders, key=lambda x: x["order_filled_timestamp"]): @@ -95,11 +96,11 @@ def balance_distribution_over_time( stake = order["safe_price"] * real_amount if order["ft_is_entry"]: fee = stake * trade.fee_open - df.loc[filled_at:, trade.pair] += real_amount + df.loc[filled_at:end_date, trade.pair] += real_amount df.loc[filled_at:, stake_currency] -= stake + fee else: fee = stake * trade.fee_close - df.loc[filled_at:, trade.pair] -= real_amount + df.loc[filled_at:end_date, trade.pair] -= real_amount df.loc[filled_at:, stake_currency] += stake - fee df = df.round(14) From cbd1a4c06072f3c3dd2d962882eb172527efb0c0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 27 Dec 2025 12:25:01 +0100 Subject: [PATCH 074/315] chore: minor improvements --- freqtrade/data/btanalysis/trade_parallelism.py | 1 + freqtrade/optimize/optimize_reports/optimize_reports.py | 2 +- freqtrade/util/migrations/migrate_wallet_history.py | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index 70b1ea7da..aed8aaf4b 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -103,5 +103,6 @@ def balance_distribution_over_time( df.loc[filled_at:end_date, trade.pair] -= real_amount df.loc[filled_at:, stake_currency] += stake - fee + # Round to avoid floating point issues df = df.round(14) return df diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index 53a40b519..d260916c1 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -36,7 +36,7 @@ def convert_bt_wallet_collection(wallet_captures: list[tuple]) -> DataFrame: (date, currency, price, balance). """ if len(wallet_captures) == 0: - return [] + return DataFrame() return DataFrame( wallet_captures, columns=["date", "currency", "price", "balance"], diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index ec6095aee..371493ea7 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -109,7 +109,7 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance WalletHistory.session.bulk_save_objects(wallet_entries) WalletHistory.session.commit() KeyValueStore.store_value("wallet_history_migration_date", dt_now()) - print(f"Successfully created {len(wallet_entries)} wallet balance records") + logger.info(f"Successfully created {len(wallet_entries)} wallet balance records") except Exception as e: WalletHistory.session.rollback() - print(f"Error saving wallet balance records: {e}") + logger.error(f"Error saving wallet balance records: {e}") From 7b49dc8c115190ca5f8335240c5adfa59cbc19d9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 2 Jan 2026 18:23:00 +0100 Subject: [PATCH 075/315] chore: improve optimize reports stability --- freqtrade/optimize/optimize_reports/bt_output.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index 026052ae6..754ed6b9f 100644 --- a/freqtrade/optimize/optimize_reports/bt_output.py +++ b/freqtrade/optimize/optimize_reports/bt_output.py @@ -287,9 +287,9 @@ def text_table_add_metrics(strat_results: dict) -> None: if "trading_mode" in strat_results else [] ) - + wallet_metrics: list[tuple[str, str]] = [] if wallet_stats := strat_results.get("wallet_stats"): - wallet_metrics = ( + wallet_metrics = [ ( "Min/Max balance realized", f"{fmt_coin(strat_results['csum_min'], stake)} / " @@ -304,7 +304,7 @@ def text_table_add_metrics(strat_results: dict) -> None: "Min/Max balance dates", f"{wallet_stats['low_date']} / {wallet_stats['high_date']}", ), - ) + ] # 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 From 6e3c8508072391d2e1cd19c36aced2f35b737863 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Jan 2026 14:10:43 +0100 Subject: [PATCH 076/315] chore: minor nitpick changes --- freqtrade/rpc/api_server/api_backtest.py | 4 ++-- freqtrade/wallets.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index ae50db978..ab451cfbd 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -374,11 +374,11 @@ def api_get_backtest_wallet(file: str, strategy: str, config=Depends(get_config) file_abs = (bt_results_base / file).with_suffix(".zip") # Ensure file is in backtest_results directory if not is_file_in_dir(file_abs, bt_results_base): - raise HTTPException(status_code=404, detail="File not found.") + raise HTTPException(status_code=400, detail="Unable to retrieve wallet history.") results = get_backtest_wallet_change(file_abs, strategy) if results is None: - raise HTTPException(status_code=404, detail="File not found.") + raise HTTPException(status_code=404, detail="Unable to retrieve wallet history.") # Consolidate the wallet to the base currency results.loc[:, "total"] = results["price"] * results["balance"] results = results.groupby(["date", "__date_ts"]).agg({"total": "sum"}).reset_index() diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 42187eac9..66c86e63b 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -453,7 +453,7 @@ class Wallets: if self._is_backtest: # only record in live mode. return - timestamp = dt_floor_day(datetime.now()) + timestamp = dt_floor_day(dt_now()) # Record total balances for all currencies for wallet in self.get_all_balances().values(): From 97e7939a30f92fb2ccb580ea9b76bae187d44e23 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Jan 2026 14:15:26 +0100 Subject: [PATCH 077/315] chore: improve wallet capturing performance --- freqtrade/wallets.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 66c86e63b..7f858735b 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -456,6 +456,7 @@ class Wallets: timestamp = dt_floor_day(dt_now()) # Record total balances for all currencies + wallet_records = [] for wallet in self.get_all_balances().values(): # TODO: exclude minimal balances price = self._exchange.get_conversion_rate(wallet.currency, self._stake_currency) @@ -465,7 +466,7 @@ class Wallets: price=price, balance=wallet.total, ) - WalletHistory.session.add(wallet_record) + wallet_records.append(wallet_record) for position in self.get_all_positions().values(): price = self._exchange.get_conversion_rate(position.symbol, self._stake_currency) @@ -475,5 +476,10 @@ class Wallets: price=price, balance=position.position, ) - WalletHistory.session.add(position_record) - WalletHistory.session.commit() + wallet_records.append(position_record) + try: + WalletHistory.session.bulk_save_objects(wallet_records) + WalletHistory.session.commit() + except Exception as e: + WalletHistory.session.rollback() + logger.error(f"Error saving wallet balance records: {e}") From 0f9cab4231740e00081861acdd65bd423eec3008 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 4 Jan 2026 14:35:16 +0100 Subject: [PATCH 078/315] chore: fix edge-case bug for empty pairlist --- freqtrade/util/migrations/migrate_wallet_history.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 371493ea7..502a3b887 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -64,6 +64,11 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance x[col] = x["open"] dfs.append(x[[col]]) + if not dfs: + logger.warning( + "No OHLCV data available for the trading pairs; skipping wallet history migration." + ) + return merged = pd.concat(dfs, axis=1) balance_dist = balance_dist.join(merged, how="left") From c884fd20ea07ac4bfd6ff8e6f6dfc4489b98f158 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 5 Jan 2026 09:48:32 +0100 Subject: [PATCH 079/315] chore: add repr output for wallet_history --- freqtrade/persistence/wallet_history.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/freqtrade/persistence/wallet_history.py b/freqtrade/persistence/wallet_history.py index 8fbd81661..aa72ea3ef 100644 --- a/freqtrade/persistence/wallet_history.py +++ b/freqtrade/persistence/wallet_history.py @@ -25,3 +25,9 @@ class WalletHistory(ModelBase): # Ensure one record per currency per day UniqueConstraint("timestamp", "currency", name="unique_wallet_daily"), ) + + def __repr__(self) -> str: + return ( + f"WalletHistory(timestamp={self.timestamp}, currency={self.currency}, " + f"price={self.price}, balance={self.balance})" + ) From c12c2177cd62dfe28043b2353db4ccecc7569162 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 5 Jan 2026 10:04:37 +0100 Subject: [PATCH 080/315] test: add tests for wallets_migration --- tests/util/test_historic_wallets_migration.py | 425 ++++++++++++++++++ 1 file changed, 425 insertions(+) create mode 100644 tests/util/test_historic_wallets_migration.py diff --git a/tests/util/test_historic_wallets_migration.py b/tests/util/test_historic_wallets_migration.py new file mode 100644 index 000000000..5c0b17876 --- /dev/null +++ b/tests/util/test_historic_wallets_migration.py @@ -0,0 +1,425 @@ +from datetime import datetime, timedelta +from unittest.mock import MagicMock + +import pytest + +from freqtrade.enums import CandleType +from freqtrade.persistence import Trade +from freqtrade.persistence.key_value_store import KeyValueStore +from freqtrade.persistence.trade_model import Order +from freqtrade.persistence.wallet_history import WalletHistory +from freqtrade.util.datetime_helpers import dt_now, dt_utc +from freqtrade.util.migrations.migrate_wallet_history import ( + _migrate_wallet_history, + migrate_wallet_history, +) +from tests.conftest import EXMS, generate_test_data, get_patched_exchange, log_has_re + + +def create_mock_trade_for_wallet(fee, pair: str, open_date: datetime, close_date: datetime): + """Create a closed trade for wallet history testing.""" + trade = Trade( + pair=pair, + stake_amount=100.0, + amount=10.0, + amount_requested=10.0, + fee_open=fee.return_value, + fee_close=fee.return_value, + open_rate=10.0, + close_rate=11.0, + close_profit=0.1, + close_profit_abs=9.5, + exchange="binance", + is_open=False, + strategy="TestStrategy", + timeframe=5, + open_date=open_date, + close_date=close_date, + is_short=False, + ) + order_entry = Order( + ft_order_side="buy", + ft_pair=pair, + ft_is_open=False, + ft_amount=10.0, + ft_price=10.0, + order_id=f"order_{pair}_entry", + status="closed", + symbol=pair, + order_type="limit", + side="buy", + price=10.0, + average=10.0, + amount=10.0, + filled=10.0, + remaining=0.0, + order_date=open_date, + order_filled_date=open_date, + ) + + order_exit = Order( + ft_order_side="sell", + ft_pair=pair, + ft_is_open=False, + ft_amount=10.0, + ft_price=11.0, + order_id=f"order_{pair}_exit", + status="closed", + symbol=pair, + order_type="limit", + side="sell", + price=11.0, + average=11.0, + amount=10.0, + filled=10.0, + remaining=0.0, + order_date=close_date, + order_filled_date=close_date, + ) + + trade.orders.append(order_entry) + trade.orders.append(order_exit) + return trade + + +@pytest.mark.usefixtures("init_persistence") +def test_migrate_wallet_history_skips_when_no_ohlcv_history(mocker, default_conf_usdt): + """Test that migration is skipped when exchange doesn't support OHLCV history.""" + exchange = MagicMock() + exchange.get_option.return_value = False # ohlcv_has_history = False + + migrate_mock = mocker.patch( + "freqtrade.util.migrations.migrate_wallet_history._migrate_wallet_history" + ) + + migrate_wallet_history(default_conf_usdt, exchange, 1000.0) + + # Should return early without setting the migration flag + assert KeyValueStore.get_int_value("wallet_history_migration") is None + assert not migrate_mock.called + + +@pytest.mark.usefixtures("init_persistence") +def test_migrate_wallet_history_skips_when_already_migrated(mocker, default_conf_usdt): + """Test that migration is skipped if already completed.""" + exchange = MagicMock() + exchange.get_option.return_value = True + + migrate_mock = mocker.patch( + "freqtrade.util.migrations.migrate_wallet_history._migrate_wallet_history" + ) + + # Set migration as already completed + KeyValueStore.store_value("wallet_history_migration", 1) + + migrate_wallet_history(default_conf_usdt, exchange, 1000.0) + # Should not call any migration logic + assert KeyValueStore.get_int_value("wallet_history_migration") == 1 + assert not migrate_mock.called + + +@pytest.mark.usefixtures("init_persistence") +def test_migrate_wallet_history_no_trades(default_conf_usdt): + """Test migration with no trades in database.""" + exchange = MagicMock() + exchange.get_option.return_value = True + + # Set bot_start_time + KeyValueStore.store_value("bot_start_time", dt_now() - timedelta(days=5)) + + migrate_wallet_history(default_conf_usdt, exchange, 1000.0) + + # Should complete migration (flag set) but no wallet entries + assert KeyValueStore.get_int_value("wallet_history_migration") == 1 + assert WalletHistory.session.query(WalletHistory).count() == 0 + + +@pytest.mark.usefixtures("init_persistence") +def test_migrate_wallet_history_with_trades(default_conf_usdt, fee, time_machine, markets): + """Test migration with trades creates wallet history entries.""" + start_time = dt_utc(2024, 1, 10, 12, 0, 0) + time_machine.move_to(start_time, tick=False) + + # Bot started 10 days ago + bot_start = start_time - timedelta(days=10) + KeyValueStore.store_value("bot_start_time", bot_start) + + # Create mock trades with dates within the range + trade_open = start_time - timedelta(days=5) + trade_close = start_time - timedelta(days=3) + trade1 = create_mock_trade_for_wallet( + fee, + "ETH/USDT", + open_date=trade_open, + close_date=trade_close, + ) + Trade.session.add(trade1) + Trade.commit() + + # Generate mock OHLCV data starting from bot_start + candle_type = default_conf_usdt.get("candle_type_def", CandleType.SPOT) + ohlcv_df = generate_test_data("1d", size=15, start=bot_start.strftime("%Y-%m-%d")) + ohlcv_data = {("ETH/USDT", "1d", candle_type): ohlcv_df} + + exchange = MagicMock() + exchange.get_option.return_value = True + exchange.markets = markets + exchange.refresh_latest_ohlcv.return_value = ohlcv_data + + migrate_wallet_history(default_conf_usdt, exchange, 1000.0) + + # Should complete migration + assert KeyValueStore.get_int_value("wallet_history_migration") == 1 + + # Should have created wallet history entries + wallet_entries = WalletHistory.session.query(WalletHistory).all() + assert len(wallet_entries) > 0 + + +@pytest.mark.usefixtures("init_persistence") +def test_migrate_wallet_history_with_multiple_pairs(default_conf_usdt, fee, time_machine, markets): + """Test migration with multiple trading pairs.""" + start_time = dt_utc(2024, 1, 15, 12, 0, 0) + time_machine.move_to(start_time, tick=False) + + # Bot started 15 days ago + bot_start = start_time - timedelta(days=15) + KeyValueStore.store_value("bot_start_time", bot_start) + + # Create mock trades for multiple pairs within the date range + trade1 = create_mock_trade_for_wallet( + fee, + "ETH/USDT", + open_date=start_time - timedelta(days=10), + close_date=start_time - timedelta(days=6), + ) + trade2 = create_mock_trade_for_wallet( + fee, + "BTC/USDT", + open_date=start_time - timedelta(days=7), + close_date=start_time - timedelta(days=5), + ) + Trade.session.add(trade1) + Trade.session.add(trade2) + Trade.commit() + + # Generate mock OHLCV data for both pairs starting from bot_start + candle_type = default_conf_usdt.get("candle_type_def", CandleType.SPOT) + ohlcv_data = {} + ohlcv_data[("ETH/USDT", "1d", candle_type)] = generate_test_data( + "1d", size=20, start=bot_start.strftime("%Y-%m-%d") + ) + + ohlcv_data[("BTC/USDT", "1d", candle_type)] = generate_test_data( + "1d", size=20, start=bot_start.strftime("%Y-%m-%d") + ) + + exchange = MagicMock() + exchange.get_option.return_value = True + exchange.markets = markets + exchange.refresh_latest_ohlcv.return_value = ohlcv_data + + migrate_wallet_history(default_conf_usdt, exchange, 1000.0) + + # Should complete migration + assert KeyValueStore.get_int_value("wallet_history_migration") == 1 + + # Should have wallet history entries + wallet_entries = WalletHistory.session.query(WalletHistory).all() + assert len(wallet_entries) > 0 + + # Check that stake currency (USDT) entries exist + usdt_entries = [e for e in wallet_entries if e.currency == "USDT"] + assert len(usdt_entries) > 0 + assert len(wallet_entries) > len(usdt_entries) + + # Stake currency should have price = 1.0 + for entry in usdt_entries: + assert entry.price == 1.0 + + eth_entries = [e for e in wallet_entries if e.currency == "ETH"] + btc_entries = [e for e in wallet_entries if e.currency == "BTC"] + assert len(eth_entries) == 4 + assert len(btc_entries) == 2 + assert all(entry.price and entry.price != 1.0 for entry in eth_entries) + assert all(entry.price and entry.price != 1.0 for entry in btc_entries) + assert all(entry.balance == 10 for entry in btc_entries) + + +@pytest.mark.usefixtures("init_persistence") +def test_migrate_wallet_history_pair_not_in_markets( + default_conf_usdt, caplog, fee, time_machine, markets +): + """Test migration handles pairs that are not in exchange markets.""" + start_time = dt_utc(2024, 1, 10, 12, 0, 0) + time_machine.move_to(start_time, tick=False) + + # Bot started 10 days ago + bot_start = start_time - timedelta(days=10) + KeyValueStore.store_value("bot_start_time", bot_start) + + # Create a trade with a pair that won't be in markets + trade1 = create_mock_trade_for_wallet( + fee, + "UNKNOWN/USDT", + open_date=start_time - timedelta(days=5), + close_date=start_time - timedelta(days=3), + ) + Trade.session.add(trade1) + Trade.commit() + + exchange = MagicMock() + exchange.get_option.return_value = True + exchange.markets = markets + exchange.refresh_latest_ohlcv.return_value = {} + + migrate_wallet_history(default_conf_usdt, exchange, 1000.0) + assert log_has_re("No OHLCV data available for .*", caplog) + + +@pytest.mark.usefixtures("init_persistence") +def test_migrate_wallet_history_stores_migration_date( + default_conf_usdt, fee, time_machine, markets +): + """Test that migration stores the migration date.""" + start_time = dt_utc(2024, 1, 10, 12, 0, 0) + time_machine.move_to(start_time, tick=False) + + # Bot started 10 days ago + bot_start = start_time - timedelta(days=10) + KeyValueStore.store_value("bot_start_time", bot_start) + + # Create a trade + trade1 = create_mock_trade_for_wallet( + fee, + "ETH/USDT", + open_date=start_time - timedelta(days=5), + close_date=start_time - timedelta(days=3), + ) + Trade.session.add(trade1) + Trade.commit() + + candle_type = default_conf_usdt.get("candle_type_def", CandleType.SPOT) + ohlcv_data = { + ("ETH/USDT", "1d", candle_type): generate_test_data( + "1d", size=15, start=bot_start.strftime("%Y-%m-%d") + ) + } + + exchange = MagicMock() + exchange.get_option.return_value = True + exchange.markets = markets + exchange.refresh_latest_ohlcv.return_value = ohlcv_data + + migrate_wallet_history(default_conf_usdt, exchange, 1000.0) + + # Check migration date is stored + migration_date = KeyValueStore.get_datetime_value("wallet_history_migration_date") + assert migration_date is not None + + +@pytest.mark.usefixtures("init_persistence") +def test_internal_migrate_wallet_history_empty_trades(default_conf_usdt, time_machine): + """Test _migrate_wallet_history returns early when no trades exist.""" + start_time = dt_utc(2024, 1, 1, 12, 0, 0) + time_machine.move_to(start_time, tick=False) + + # Set bot_start_time + KeyValueStore.store_value("bot_start_time", start_time - timedelta(days=5)) + + exchange = MagicMock() + exchange.get_option.return_value = True + exchange.markets = {} + exchange.refresh_latest_ohlcv.return_value = {} + + # Call internal function directly with no trades + _migrate_wallet_history(default_conf_usdt, exchange, 1000.0) + + # refresh_latest_ohlcv should not be called when there are no trades + exchange.refresh_latest_ohlcv.assert_not_called() + + +@pytest.mark.usefixtures("init_persistence") +def test_migrate_wallet_history_with_patched_exchange(mocker, default_conf_usdt, fee, time_machine): + """Test migration using get_patched_exchange helper.""" + start_time = dt_utc(2024, 1, 10, 12, 0, 0) + time_machine.move_to(start_time, tick=False) + + # Bot started 10 days ago + bot_start = start_time - timedelta(days=10) + KeyValueStore.store_value("bot_start_time", bot_start) + + # Create a trade + trade1 = create_mock_trade_for_wallet( + fee, + "ETH/USDT", + open_date=start_time - timedelta(days=5), + close_date=start_time - timedelta(days=3), + ) + Trade.session.add(trade1) + Trade.commit() + + # Generate mock OHLCV data starting from bot_start + candle_type = default_conf_usdt.get("candle_type_def", CandleType.SPOT) + ohlcv_df = generate_test_data("1d", size=15, start=bot_start.strftime("%Y-%m-%d")) + ohlcv_data = {("ETH/USDT", "1d", candle_type): ohlcv_df} + + # Mock exchange methods + mocker.patch.multiple( + EXMS, + get_option=MagicMock(return_value=True), + refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data), + ) + + exchange = get_patched_exchange(mocker, default_conf_usdt) + + migrate_wallet_history(default_conf_usdt, exchange, 1000.0) + + # Should complete migration + assert KeyValueStore.get_int_value("wallet_history_migration") == 1 + + +@pytest.mark.usefixtures("init_persistence") +def test_migrate_wallet_history_db_error_handling( + mocker, default_conf_usdt, fee, time_machine, markets +): + """Test that database errors are handled gracefully.""" + start_time = dt_utc(2024, 1, 10, 12, 0, 0) + time_machine.move_to(start_time, tick=False) + + # Bot started 10 days ago + bot_start = start_time - timedelta(days=10) + KeyValueStore.store_value("bot_start_time", bot_start) + + # Create a trade + trade1 = create_mock_trade_for_wallet( + fee, + "ETH/USDT", + open_date=start_time - timedelta(days=5), + close_date=start_time - timedelta(days=3), + ) + Trade.session.add(trade1) + Trade.commit() + + candle_type = default_conf_usdt.get("candle_type_def", CandleType.SPOT) + ohlcv_data = { + ("ETH/USDT", "1d", candle_type): generate_test_data( + "1d", size=15, start=bot_start.strftime("%Y-%m-%d") + ) + } + + exchange = MagicMock() + exchange.get_option.return_value = True + exchange.markets = markets + exchange.refresh_latest_ohlcv.return_value = ohlcv_data + + # Mock bulk_save_objects to raise an exception + mocker.patch.object( + WalletHistory.session, "bulk_save_objects", side_effect=Exception("DB Error") + ) + + # Should not raise exception, but handle error gracefully + migrate_wallet_history(default_conf_usdt, exchange, 1000.0) + + # Migration flag should still be set even after error in _migrate + assert KeyValueStore.get_int_value("wallet_history_migration") == 1 From 03fd0575ce4ae0c54dec9c1c7ebc42a01b50e8f2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 5 Jan 2026 10:08:54 +0100 Subject: [PATCH 081/315] test: simplify imports --- tests/util/test_historic_wallets_migration.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/util/test_historic_wallets_migration.py b/tests/util/test_historic_wallets_migration.py index 5c0b17876..eacdb8889 100644 --- a/tests/util/test_historic_wallets_migration.py +++ b/tests/util/test_historic_wallets_migration.py @@ -4,11 +4,8 @@ from unittest.mock import MagicMock import pytest from freqtrade.enums import CandleType -from freqtrade.persistence import Trade -from freqtrade.persistence.key_value_store import KeyValueStore -from freqtrade.persistence.trade_model import Order -from freqtrade.persistence.wallet_history import WalletHistory -from freqtrade.util.datetime_helpers import dt_now, dt_utc +from freqtrade.persistence import KeyValueStore, Order, Trade, WalletHistory +from freqtrade.util import dt_now, dt_utc from freqtrade.util.migrations.migrate_wallet_history import ( _migrate_wallet_history, migrate_wallet_history, From 323f42fc5329a707b6fa1f7ff55c6471e20f23fc Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 5 Jan 2026 11:11:45 +0100 Subject: [PATCH 082/315] fix: Don't round date up to next date it'll cause a record in the future eventually. --- freqtrade/data/btanalysis/trade_parallelism.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index aed8aaf4b..dc242587e 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -6,7 +6,6 @@ import pandas as pd from freqtrade.constants import IntOrInf from freqtrade.exchange.exchange_utils_timeframe import ( - timeframe_to_next_date, timeframe_to_prev_date, timeframe_to_resample_freq, ) @@ -81,7 +80,7 @@ def balance_distribution_over_time( Each column will contain the amount of the currency at the given time """ min_date_res = timeframe_to_prev_date(timeframe, min_date) - max_date_res = timeframe_to_next_date(timeframe, max_date) + max_date_res = timeframe_to_prev_date(timeframe, max_date) index = pd.date_range(min_date_res, max_date_res, freq=timeframe_to_resample_freq(timeframe)) df = pd.DataFrame(index=index) df[stake_currency] = float(start_balance) From 1d7b1cd4ea1601a86262ccfa434d4eb600f1129b Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 5 Jan 2026 11:50:39 +0100 Subject: [PATCH 083/315] test: improve test to make it more realistic --- tests/conftest.py | 6 ++++-- tests/util/test_historic_wallets_migration.py | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index abd15a6a1..93d34fe18 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -169,10 +169,12 @@ def generate_trades_history(n_rows, start_date: datetime | None = None, days=5): return df -def generate_test_data(timeframe: str, size: int, start: str = "2020-07-05", random_seed=42): +def generate_test_data( + timeframe: str, size: int, start: str = "2020-07-05", random_seed=42, base=20 +): np.random.seed(random_seed) - base = np.random.normal(20, 2, size=size) + base = np.random.normal(base, 2, size=size) if timeframe == "1y": date = pd.date_range(start, periods=size, freq="1YS", tz="UTC") elif timeframe == "1M": diff --git a/tests/util/test_historic_wallets_migration.py b/tests/util/test_historic_wallets_migration.py index eacdb8889..e5f40bb8f 100644 --- a/tests/util/test_historic_wallets_migration.py +++ b/tests/util/test_historic_wallets_migration.py @@ -204,11 +204,11 @@ def test_migrate_wallet_history_with_multiple_pairs(default_conf_usdt, fee, time candle_type = default_conf_usdt.get("candle_type_def", CandleType.SPOT) ohlcv_data = {} ohlcv_data[("ETH/USDT", "1d", candle_type)] = generate_test_data( - "1d", size=20, start=bot_start.strftime("%Y-%m-%d") + "1d", size=20, start=bot_start.strftime("%Y-%m-%d"), base=1500 ) ohlcv_data[("BTC/USDT", "1d", candle_type)] = generate_test_data( - "1d", size=20, start=bot_start.strftime("%Y-%m-%d") + "1d", size=20, start=bot_start.strftime("%Y-%m-%d"), base=30000 ) exchange = MagicMock() @@ -238,8 +238,8 @@ def test_migrate_wallet_history_with_multiple_pairs(default_conf_usdt, fee, time btc_entries = [e for e in wallet_entries if e.currency == "BTC"] assert len(eth_entries) == 4 assert len(btc_entries) == 2 - assert all(entry.price and entry.price != 1.0 for entry in eth_entries) - assert all(entry.price and entry.price != 1.0 for entry in btc_entries) + assert all(entry.price and entry.price > 1400 and entry.price < 1600 for entry in eth_entries) + assert all(entry.price and entry.price > 29000 and entry.price < 31000 for entry in btc_entries) assert all(entry.balance == 10 for entry in btc_entries) From 175e77794c23dd58a30014f2789e29300f5a6e4a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 5 Jan 2026 11:52:01 +0100 Subject: [PATCH 084/315] chore: improve wallet migration code --- .../util/migrations/migrate_wallet_history.py | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 502a3b887..9693dfd7d 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -7,10 +7,8 @@ from freqtrade.data.btanalysis.bt_fileutils import trade_list_to_dataframe from freqtrade.data.btanalysis.trade_parallelism import balance_distribution_over_time from freqtrade.exchange import Exchange from freqtrade.exchange.exchange_utils_timeframe import timeframe_to_prev_date -from freqtrade.persistence.key_value_store import KeyValueStore -from freqtrade.persistence.trade_model import Trade -from freqtrade.persistence.wallet_history import WalletHistory -from freqtrade.util.datetime_helpers import dt_now, dt_ts +from freqtrade.persistence import KeyValueStore, Trade, WalletHistory +from freqtrade.util import dt_now, dt_ts logger = logging.getLogger(__name__) @@ -79,31 +77,46 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance [f"{p}_value" for p in pairlist_valid] + [stake_currency] ].sum(axis=1) + # Precompute column indices for faster tuple-based iteration + # Assume the first column is the index (date) + stake_idx = balance_dist.columns.get_loc(stake_currency) + pair_balance_idx = {pair: balance_dist.columns.get_loc(pair) + 1 for pair in pairlist_valid} + pair_price_idx = { + pair: balance_dist.columns.get_loc(f"{pair}_open") + 1 for pair in pairlist_valid + } + # Convert balance_dist to WalletHistory entries wallet_entries = [] - for date, row in balance_dist.iterrows(): + for row in balance_dist.itertuples(index=True, name=None): + date = row[0] + # Add stake currency entry - if not pd.isna(row[stake_currency]): + stake_balance = row[stake_idx + 1] + if not pd.isna(stake_balance): wallet_entries.append( WalletHistory( timestamp=date, currency=stake_currency, price=1.0, # Stake currency price is always 1.0 - balance=row[stake_currency], + balance=stake_balance, ) ) # Add entries for each trading pair for pair in pairlist_valid: base_currency = pair.split("/")[0] + balance_value = row[pair_balance_idx[pair]] # Only add entry if balance is not empty/NaN - if not pd.isna(row[pair]) and row[pair] > 0: - price_col = f"{pair}_open" - price = row[price_col] if not pd.isna(row[price_col]) else None + if not pd.isna(balance_value) and balance_value > 0: + price_value = row[pair_price_idx[pair]] + price = price_value if not pd.isna(price_value) else None wallet_entries.append( WalletHistory( - timestamp=date, currency=base_currency, price=price, balance=row[pair] + timestamp=date, + currency=base_currency, + price=price, + balance=balance_value, ) ) From 9f9e13cec25aeb3354d09ac7c02e9fc3423f6c36 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 5 Jan 2026 11:57:06 +0100 Subject: [PATCH 085/315] chore: add better docstring --- freqtrade/data/btanalysis/trade_parallelism.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index dc242587e..f6c756094 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -78,6 +78,17 @@ def balance_distribution_over_time( """ Return a dataframe with stake_currency and the pairlist as columns Each column will contain the amount of the currency at the given time + :param trades: Trades Dataframe - can be loaded from backtest, or created + via trade_list_to_dataframe + :param timeframe: Frequency to use for the resulting dataframe + :param min_date: start date + :param max_date: End date (will be rounded down to timeframe) + :param stake_currency: The stake currency + :param start_balance: Starting balance in stake currency + :param pairlist: List of trading pairs to include in the dataframe + Can be obtained via trade_df["pair"].unique() + For pairs without trades, the column will be all zeros + :return: Dataframe with balance distribution over time """ min_date_res = timeframe_to_prev_date(timeframe, min_date) max_date_res = timeframe_to_prev_date(timeframe, max_date) From 27c7a375310adb8c34c50ba45dfb03c5f59056d8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 5 Jan 2026 13:05:00 +0100 Subject: [PATCH 086/315] test: add test for balance_distribution_over_time --- tests/data/test_btanalysis.py | 187 ++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index c869e6a92..117efd956 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -20,6 +20,7 @@ from freqtrade.data.btanalysis import ( load_trades, load_trades_from_db, ) +from freqtrade.data.btanalysis.trade_parallelism import balance_distribution_over_time from freqtrade.data.history import load_data, load_pair_history from freqtrade.data.metrics import ( calculate_cagr, @@ -649,3 +650,189 @@ def test_load_file_from_zip(tmp_path): with pytest.raises(ValueError, match=r"File .* not found in zip.*"): load_file_from_zip(zip_file, "testfile55.txt") + + +@pytest.mark.parametrize("is_short", [False, True]) +def test_balance_distribution_over_time(is_short): + """ + Test balance_distribution_over_time for both long and short trades. + """ + # Create a minimal trades DataFrame with 4 trades over time + # Base dates for trades + start_date = dt_utc(2023, 1, 1) + base_date = start_date + timedelta(hours=15) + stake_currency = "USDT" + start_balance = 1000.0 + fee = 0.001 # 0.1% fee + + # Create trades spanning different time periods + trades_data = { + "pair": ["BTC/USDT", "ETH/USDT", "XRP/USDT", "LTC/USDT"], + "stake_amount": [100.0, 150.0, 80.0, 120.0], + "open_date": [ + base_date, + base_date + timedelta(hours=2), + base_date + timedelta(hours=5), + base_date + timedelta(hours=8), + ], + "close_date": [ + base_date + timedelta(hours=3), + base_date + timedelta(hours=6), + base_date + timedelta(hours=9), + base_date + timedelta(hours=12), + ], + "open_rate": [40000.0, 2000.0, 0.5, 100.0], + "close_rate": [41000.0, 2100.0, 0.52, 105.0], + "fee_open": [fee, fee, fee, fee], + "fee_close": [fee, fee, fee, fee], + "is_short": [is_short, is_short, is_short, is_short], + "leverage": [1.0, 1.0, 1.0, 1.0], + "orders": [ + # Trade 1: BTC/USDT - entry at 40000, exit at 41000 + [ + { + "amount": 0.0025, # 100 / 40000 + "filled": 0.0025, + "safe_price": 40000.0, + "ft_order_side": "sell" if is_short else "buy", + "order_filled_timestamp": int(base_date.timestamp() * 1000), + "ft_is_entry": True, + }, + { + "amount": 0.0025, + "filled": 0.0025, + "safe_price": 41000.0, + "ft_order_side": "buy" if is_short else "sell", + "order_filled_timestamp": int( + (base_date + timedelta(hours=3)).timestamp() * 1000 + ), + "ft_is_entry": False, + }, + ], + # Trade 2: ETH/USDT - entry at 2000, exit at 2100 + [ + { + "amount": 0.075, # 150 / 2000 + "filled": 0.075, + "safe_price": 2000.0, + "ft_order_side": "sell" if is_short else "buy", + "order_filled_timestamp": int( + (base_date + timedelta(hours=2)).timestamp() * 1000 + ), + "ft_is_entry": True, + }, + { + "amount": 0.075, + "filled": 0.075, + "safe_price": 2100.0, + "ft_order_side": "buy" if is_short else "sell", + "order_filled_timestamp": int( + (base_date + timedelta(hours=6)).timestamp() * 1000 + ), + "ft_is_entry": False, + }, + ], + # Trade 3: XRP/USDT - entry at 0.5, exit at 0.52 + [ + { + "amount": 160.0, # 80 / 0.5 + "filled": 160.0, + "safe_price": 0.5, + "ft_order_side": "sell" if is_short else "buy", + "order_filled_timestamp": int( + (base_date + timedelta(hours=5)).timestamp() * 1000 + ), + "ft_is_entry": True, + }, + { + "amount": 160.0, + "filled": 160.0, + "safe_price": 0.52, + "ft_order_side": "buy" if is_short else "sell", + "order_filled_timestamp": int( + (base_date + timedelta(hours=9)).timestamp() * 1000 + ), + "ft_is_entry": False, + }, + ], + # Trade 4: LTC/USDT - entry at 100, exit at 105 + [ + { + "amount": 1.2, # 120 / 100 + "filled": 1.2, + "safe_price": 100.0, + "ft_order_side": "sell" if is_short else "buy", + "order_filled_timestamp": int( + (base_date + timedelta(hours=8)).timestamp() * 1000 + ), + "ft_is_entry": True, + }, + { + "amount": 1.2, + "filled": 1.2, + "safe_price": 105.0, + "ft_order_side": "buy" if is_short else "sell", + "order_filled_timestamp": int( + (base_date + timedelta(hours=12)).timestamp() * 1000 + ), + "ft_is_entry": False, + }, + ], + ], + } + + trades_df = DataFrame(trades_data) + pairlist = ["BTC/USDT", "ETH/USDT", "XRP/USDT", "LTC/USDT"] + + min_date = start_date + max_date = start_date + timedelta(hours=35) + + result = balance_distribution_over_time( + trades=trades_df, + min_date=min_date, + max_date=max_date, + timeframe="1h", + stake_currency=stake_currency, + start_balance=start_balance, + pairlist=pairlist, + ) + + # Verify basic structure + assert isinstance(result, DataFrame) + assert stake_currency in result.columns + for pair in pairlist: + assert pair in result.columns + + # Verify the index is a DatetimeIndex + assert isinstance(result.index, Timestamp.__class__.__bases__[0]) + + # Verify we have entries over the full time period (36h) + assert len(result) == 36 + + # First trade opens 15h after the start date + assert result.iloc[0][stake_currency] == 1000 + expected_first_balance = start_balance - (100.0 + 100.0 * fee) + assert result.iloc[15][stake_currency] == pytest.approx(expected_first_balance) + + # Check that pair columns have non-zero values during trade periods + # Trade 1 (BTC/USDT) is open from hour 15 to hour 18 + # At hour 16, BTC/USDT should have position + btc_during_trade = result.loc[base_date + timedelta(hours=1), "BTC/USDT"] + assert btc_during_trade > 0, "Trade should have positive position during open period" + + # After Trade 1 closes at hour 3, BTC/USDT position should be 0 + btc_after_close = result.loc[base_date + timedelta(hours=4) :, "BTC/USDT"] + assert all(btc_after_close == 0), "Position should be 0 after trade closes" + + # Final stake currency should reflect all trades' cash flows minus fees + # The function tracks cash flow: entries subtract stake, exits add stake + # Both long and short use the same formula based on order prices + final_balance = result.iloc[-1][stake_currency] + + # Verify the balance changed (trades had effect) + assert final_balance != start_balance, "Balance should change after trading" + + # Since all exit prices > entry prices, exits return more cash than entries spent + # This means final balance > start balance for both long and short trades + # (the function tracks cash flow, not P&L from long/short perspective) + assert final_balance > start_balance, "Exit prices > entry prices should increase balance" From 4904c7b9fd0d974d3262e25ff6676eac4d51ef54 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 5 Jan 2026 13:06:53 +0100 Subject: [PATCH 087/315] refactor: split trade_parallelism trades into their own testfile --- tests/data/test_btanalysis.py | 199 ------------------------- tests/data/test_trade_parallelism.py | 208 +++++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 199 deletions(-) create mode 100644 tests/data/test_trade_parallelism.py diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 117efd956..318918044 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -10,7 +10,6 @@ from freqtrade.configuration import TimeRange from freqtrade.constants import LAST_BT_RESULT_FN from freqtrade.data.btanalysis import ( BT_DATA_COLUMNS, - analyze_trade_parallelism, extract_trades_of_period, get_latest_backtest_filename, get_latest_hyperopt_file, @@ -20,7 +19,6 @@ from freqtrade.data.btanalysis import ( load_trades, load_trades_from_db, ) -from freqtrade.data.btanalysis.trade_parallelism import balance_distribution_over_time from freqtrade.data.history import load_data, load_pair_history from freqtrade.data.metrics import ( calculate_cagr, @@ -210,17 +208,6 @@ def test_extract_trades_of_period(testdatadir): assert trades1.iloc[-1].close_date == datetime(2017, 11, 14, 15, 25, 0, tzinfo=UTC) -def test_analyze_trade_parallelism(testdatadir): - filename = testdatadir / "backtest_results/backtest-result.json" - bt_data = load_backtest_data(filename) - - res = analyze_trade_parallelism(bt_data, "5m") - assert isinstance(res, DataFrame) - assert "open_trades" in res.columns - assert res["open_trades"].max() == 3 - assert res["open_trades"].min() == 0 - - def test_load_trades(default_conf, mocker): db_mock = mocker.patch( "freqtrade.data.btanalysis.bt_fileutils.load_trades_from_db", MagicMock() @@ -650,189 +637,3 @@ def test_load_file_from_zip(tmp_path): with pytest.raises(ValueError, match=r"File .* not found in zip.*"): load_file_from_zip(zip_file, "testfile55.txt") - - -@pytest.mark.parametrize("is_short", [False, True]) -def test_balance_distribution_over_time(is_short): - """ - Test balance_distribution_over_time for both long and short trades. - """ - # Create a minimal trades DataFrame with 4 trades over time - # Base dates for trades - start_date = dt_utc(2023, 1, 1) - base_date = start_date + timedelta(hours=15) - stake_currency = "USDT" - start_balance = 1000.0 - fee = 0.001 # 0.1% fee - - # Create trades spanning different time periods - trades_data = { - "pair": ["BTC/USDT", "ETH/USDT", "XRP/USDT", "LTC/USDT"], - "stake_amount": [100.0, 150.0, 80.0, 120.0], - "open_date": [ - base_date, - base_date + timedelta(hours=2), - base_date + timedelta(hours=5), - base_date + timedelta(hours=8), - ], - "close_date": [ - base_date + timedelta(hours=3), - base_date + timedelta(hours=6), - base_date + timedelta(hours=9), - base_date + timedelta(hours=12), - ], - "open_rate": [40000.0, 2000.0, 0.5, 100.0], - "close_rate": [41000.0, 2100.0, 0.52, 105.0], - "fee_open": [fee, fee, fee, fee], - "fee_close": [fee, fee, fee, fee], - "is_short": [is_short, is_short, is_short, is_short], - "leverage": [1.0, 1.0, 1.0, 1.0], - "orders": [ - # Trade 1: BTC/USDT - entry at 40000, exit at 41000 - [ - { - "amount": 0.0025, # 100 / 40000 - "filled": 0.0025, - "safe_price": 40000.0, - "ft_order_side": "sell" if is_short else "buy", - "order_filled_timestamp": int(base_date.timestamp() * 1000), - "ft_is_entry": True, - }, - { - "amount": 0.0025, - "filled": 0.0025, - "safe_price": 41000.0, - "ft_order_side": "buy" if is_short else "sell", - "order_filled_timestamp": int( - (base_date + timedelta(hours=3)).timestamp() * 1000 - ), - "ft_is_entry": False, - }, - ], - # Trade 2: ETH/USDT - entry at 2000, exit at 2100 - [ - { - "amount": 0.075, # 150 / 2000 - "filled": 0.075, - "safe_price": 2000.0, - "ft_order_side": "sell" if is_short else "buy", - "order_filled_timestamp": int( - (base_date + timedelta(hours=2)).timestamp() * 1000 - ), - "ft_is_entry": True, - }, - { - "amount": 0.075, - "filled": 0.075, - "safe_price": 2100.0, - "ft_order_side": "buy" if is_short else "sell", - "order_filled_timestamp": int( - (base_date + timedelta(hours=6)).timestamp() * 1000 - ), - "ft_is_entry": False, - }, - ], - # Trade 3: XRP/USDT - entry at 0.5, exit at 0.52 - [ - { - "amount": 160.0, # 80 / 0.5 - "filled": 160.0, - "safe_price": 0.5, - "ft_order_side": "sell" if is_short else "buy", - "order_filled_timestamp": int( - (base_date + timedelta(hours=5)).timestamp() * 1000 - ), - "ft_is_entry": True, - }, - { - "amount": 160.0, - "filled": 160.0, - "safe_price": 0.52, - "ft_order_side": "buy" if is_short else "sell", - "order_filled_timestamp": int( - (base_date + timedelta(hours=9)).timestamp() * 1000 - ), - "ft_is_entry": False, - }, - ], - # Trade 4: LTC/USDT - entry at 100, exit at 105 - [ - { - "amount": 1.2, # 120 / 100 - "filled": 1.2, - "safe_price": 100.0, - "ft_order_side": "sell" if is_short else "buy", - "order_filled_timestamp": int( - (base_date + timedelta(hours=8)).timestamp() * 1000 - ), - "ft_is_entry": True, - }, - { - "amount": 1.2, - "filled": 1.2, - "safe_price": 105.0, - "ft_order_side": "buy" if is_short else "sell", - "order_filled_timestamp": int( - (base_date + timedelta(hours=12)).timestamp() * 1000 - ), - "ft_is_entry": False, - }, - ], - ], - } - - trades_df = DataFrame(trades_data) - pairlist = ["BTC/USDT", "ETH/USDT", "XRP/USDT", "LTC/USDT"] - - min_date = start_date - max_date = start_date + timedelta(hours=35) - - result = balance_distribution_over_time( - trades=trades_df, - min_date=min_date, - max_date=max_date, - timeframe="1h", - stake_currency=stake_currency, - start_balance=start_balance, - pairlist=pairlist, - ) - - # Verify basic structure - assert isinstance(result, DataFrame) - assert stake_currency in result.columns - for pair in pairlist: - assert pair in result.columns - - # Verify the index is a DatetimeIndex - assert isinstance(result.index, Timestamp.__class__.__bases__[0]) - - # Verify we have entries over the full time period (36h) - assert len(result) == 36 - - # First trade opens 15h after the start date - assert result.iloc[0][stake_currency] == 1000 - expected_first_balance = start_balance - (100.0 + 100.0 * fee) - assert result.iloc[15][stake_currency] == pytest.approx(expected_first_balance) - - # Check that pair columns have non-zero values during trade periods - # Trade 1 (BTC/USDT) is open from hour 15 to hour 18 - # At hour 16, BTC/USDT should have position - btc_during_trade = result.loc[base_date + timedelta(hours=1), "BTC/USDT"] - assert btc_during_trade > 0, "Trade should have positive position during open period" - - # After Trade 1 closes at hour 3, BTC/USDT position should be 0 - btc_after_close = result.loc[base_date + timedelta(hours=4) :, "BTC/USDT"] - assert all(btc_after_close == 0), "Position should be 0 after trade closes" - - # Final stake currency should reflect all trades' cash flows minus fees - # The function tracks cash flow: entries subtract stake, exits add stake - # Both long and short use the same formula based on order prices - final_balance = result.iloc[-1][stake_currency] - - # Verify the balance changed (trades had effect) - assert final_balance != start_balance, "Balance should change after trading" - - # Since all exit prices > entry prices, exits return more cash than entries spent - # This means final balance > start balance for both long and short trades - # (the function tracks cash flow, not P&L from long/short perspective) - assert final_balance > start_balance, "Exit prices > entry prices should increase balance" diff --git a/tests/data/test_trade_parallelism.py b/tests/data/test_trade_parallelism.py new file mode 100644 index 000000000..1aadf99f7 --- /dev/null +++ b/tests/data/test_trade_parallelism.py @@ -0,0 +1,208 @@ +from datetime import timedelta + +import pytest +from pandas import DataFrame, Timestamp + +from freqtrade.data.btanalysis import ( + analyze_trade_parallelism, + load_backtest_data, +) +from freqtrade.data.btanalysis.trade_parallelism import balance_distribution_over_time +from freqtrade.util import dt_utc + + +def test_analyze_trade_parallelism(testdatadir): + filename = testdatadir / "backtest_results/backtest-result.json" + bt_data = load_backtest_data(filename) + + res = analyze_trade_parallelism(bt_data, "5m") + assert isinstance(res, DataFrame) + assert "open_trades" in res.columns + assert res["open_trades"].max() == 3 + assert res["open_trades"].min() == 0 + + +@pytest.mark.parametrize("is_short", [False, True]) +def test_balance_distribution_over_time(is_short): + """ + Test balance_distribution_over_time for both long and short trades. + """ + # Create a minimal trades DataFrame with 4 trades over time + # Base dates for trades + start_date = dt_utc(2023, 1, 1) + base_date = start_date + timedelta(hours=15) + stake_currency = "USDT" + start_balance = 1000.0 + fee = 0.001 # 0.1% fee + + # Create trades spanning different time periods + trades_data = { + "pair": ["BTC/USDT", "ETH/USDT", "XRP/USDT", "LTC/USDT"], + "stake_amount": [100.0, 150.0, 80.0, 120.0], + "open_date": [ + base_date, + base_date + timedelta(hours=2), + base_date + timedelta(hours=5), + base_date + timedelta(hours=8), + ], + "close_date": [ + base_date + timedelta(hours=3), + base_date + timedelta(hours=6), + base_date + timedelta(hours=9), + base_date + timedelta(hours=12), + ], + "open_rate": [40000.0, 2000.0, 0.5, 100.0], + "close_rate": [41000.0, 2100.0, 0.52, 105.0], + "fee_open": [fee, fee, fee, fee], + "fee_close": [fee, fee, fee, fee], + "is_short": [is_short, is_short, is_short, is_short], + "leverage": [1.0, 1.0, 1.0, 1.0], + "orders": [ + # Trade 1: BTC/USDT - entry at 40000, exit at 41000 + [ + { + "amount": 0.0025, # 100 / 40000 + "filled": 0.0025, + "safe_price": 40000.0, + "ft_order_side": "sell" if is_short else "buy", + "order_filled_timestamp": int(base_date.timestamp() * 1000), + "ft_is_entry": True, + }, + { + "amount": 0.0025, + "filled": 0.0025, + "safe_price": 41000.0, + "ft_order_side": "buy" if is_short else "sell", + "order_filled_timestamp": int( + (base_date + timedelta(hours=3)).timestamp() * 1000 + ), + "ft_is_entry": False, + }, + ], + # Trade 2: ETH/USDT - entry at 2000, exit at 2100 + [ + { + "amount": 0.075, # 150 / 2000 + "filled": 0.075, + "safe_price": 2000.0, + "ft_order_side": "sell" if is_short else "buy", + "order_filled_timestamp": int( + (base_date + timedelta(hours=2)).timestamp() * 1000 + ), + "ft_is_entry": True, + }, + { + "amount": 0.075, + "filled": 0.075, + "safe_price": 2100.0, + "ft_order_side": "buy" if is_short else "sell", + "order_filled_timestamp": int( + (base_date + timedelta(hours=6)).timestamp() * 1000 + ), + "ft_is_entry": False, + }, + ], + # Trade 3: XRP/USDT - entry at 0.5, exit at 0.52 + [ + { + "amount": 160.0, # 80 / 0.5 + "filled": 160.0, + "safe_price": 0.5, + "ft_order_side": "sell" if is_short else "buy", + "order_filled_timestamp": int( + (base_date + timedelta(hours=5)).timestamp() * 1000 + ), + "ft_is_entry": True, + }, + { + "amount": 160.0, + "filled": 160.0, + "safe_price": 0.52, + "ft_order_side": "buy" if is_short else "sell", + "order_filled_timestamp": int( + (base_date + timedelta(hours=9)).timestamp() * 1000 + ), + "ft_is_entry": False, + }, + ], + # Trade 4: LTC/USDT - entry at 100, exit at 105 + [ + { + "amount": 1.2, # 120 / 100 + "filled": 1.2, + "safe_price": 100.0, + "ft_order_side": "sell" if is_short else "buy", + "order_filled_timestamp": int( + (base_date + timedelta(hours=8)).timestamp() * 1000 + ), + "ft_is_entry": True, + }, + { + "amount": 1.2, + "filled": 1.2, + "safe_price": 105.0, + "ft_order_side": "buy" if is_short else "sell", + "order_filled_timestamp": int( + (base_date + timedelta(hours=12)).timestamp() * 1000 + ), + "ft_is_entry": False, + }, + ], + ], + } + + trades_df = DataFrame(trades_data) + pairlist = ["BTC/USDT", "ETH/USDT", "XRP/USDT", "LTC/USDT"] + + min_date = start_date + max_date = start_date + timedelta(hours=35) + + result = balance_distribution_over_time( + trades=trades_df, + min_date=min_date, + max_date=max_date, + timeframe="1h", + stake_currency=stake_currency, + start_balance=start_balance, + pairlist=pairlist, + ) + + # Verify basic structure + assert isinstance(result, DataFrame) + assert stake_currency in result.columns + for pair in pairlist: + assert pair in result.columns + + # Verify the index is a DatetimeIndex + assert isinstance(result.index, Timestamp.__class__.__bases__[0]) + + # Verify we have entries over the full time period (36h) + assert len(result) == 36 + + # First trade opens 15h after the start date + assert result.iloc[0][stake_currency] == 1000 + expected_first_balance = start_balance - (100.0 + 100.0 * fee) + assert result.iloc[15][stake_currency] == pytest.approx(expected_first_balance) + + # Check that pair columns have non-zero values during trade periods + # Trade 1 (BTC/USDT) is open from hour 15 to hour 18 + # At hour 16, BTC/USDT should have position + btc_during_trade = result.loc[base_date + timedelta(hours=1), "BTC/USDT"] + assert btc_during_trade > 0, "Trade should have positive position during open period" + + # After Trade 1 closes at hour 3, BTC/USDT position should be 0 + btc_after_close = result.loc[base_date + timedelta(hours=4) :, "BTC/USDT"] + assert all(btc_after_close == 0), "Position should be 0 after trade closes" + + # Final stake currency should reflect all trades' cash flows minus fees + # The function tracks cash flow: entries subtract stake, exits add stake + # Both long and short use the same formula based on order prices + final_balance = result.iloc[-1][stake_currency] + + # Verify the balance changed (trades had effect) + assert final_balance != start_balance, "Balance should change after trading" + + # Since all exit prices > entry prices, exits return more cash than entries spent + # This means final balance > start balance for both long and short trades + # (the function tracks cash flow, not P&L from long/short perspective) + assert final_balance > start_balance, "Exit prices > entry prices should increase balance" From 3a9160aace2a0a9256cf53640554d3925cc39bea Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 5 Jan 2026 13:07:02 +0100 Subject: [PATCH 088/315] chore: simplify date import --- freqtrade/data/btanalysis/trade_parallelism.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index f6c756094..1ffa14af2 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -9,7 +9,7 @@ from freqtrade.exchange.exchange_utils_timeframe import ( timeframe_to_prev_date, timeframe_to_resample_freq, ) -from freqtrade.util.datetime_helpers import dt_from_ts +from freqtrade.util import dt_from_ts logger = logging.getLogger(__name__) From 7dfcf846d01396f3d194c45fcad19d13ff42bd96 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 5 Jan 2026 19:55:32 +0100 Subject: [PATCH 089/315] test: add asserts for backtest wallet capturing --- tests/optimize/test_backtesting.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index e051e6b3c..70b591d54 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -757,10 +757,12 @@ def test_backtest__check_trade_exit(default_conf, mocker) -> None: def test_backtest_one(default_conf, mocker, testdatadir) -> None: default_conf["use_exit_signal"] = False default_conf["max_open_trades"] = 10 + default_conf["runmode"] = RunMode.BACKTEST patch_exchange(mocker) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) + mocker.patch(f"{EXMS}.get_pair_base_currency", lambda _, x: x.split("/")[0]) backtesting = Backtesting(default_conf) backtesting._set_strategy(backtesting.strategylist[0]) pair = "UNITTEST/BTC" @@ -875,13 +877,23 @@ def test_backtest_one(default_conf, mocker, testdatadir) -> None: ln1.iloc[0]["low"], 6 ) < round(t["close_rate"], 6) < round(ln1.iloc[0]["high"], 6) + wallet_summary = result["wallet_summary"] + assert isinstance(wallet_summary, pd.DataFrame) + assert len(wallet_summary) == 255 + unique_currencies = wallet_summary["currency"].value_counts() + assert unique_currencies["BTC"] == 200 + assert unique_currencies["UNITTEST"] == 55 + @pytest.mark.parametrize("use_detail", [True, False]) def test_backtest_one_detail(default_conf_usdt, mocker, testdatadir, use_detail) -> None: default_conf_usdt["use_exit_signal"] = False + default_conf_usdt["runmode"] = RunMode.BACKTEST patch_exchange(mocker) mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=0.00001) mocker.patch(f"{EXMS}.get_max_pair_stake_amount", return_value=float("inf")) + mocker.patch(f"{EXMS}.get_pair_base_currency", lambda _, x: x.split("/")[0]) + default_conf_usdt["unfilledtimeout"] = { "entry": 11, "exit": 30, @@ -968,6 +980,12 @@ def test_backtest_one_detail(default_conf_usdt, mocker, testdatadir, use_detail) ) assert late_entry > 0 + wallet_summary = result["wallet_summary"] + assert isinstance(wallet_summary, pd.DataFrame) + assert len(wallet_summary) == 591 if use_detail else 597 + unique_currencies = wallet_summary["currency"].value_counts() + assert unique_currencies["USDT"] == 576 + assert unique_currencies["XRP"] == 15 if use_detail else 21 @pytest.mark.parametrize( From cb8d68f395202dfae14434dfdd7762db24b3fbb3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 6 Jan 2026 10:33:34 +0100 Subject: [PATCH 090/315] test: add test for record_wallet_state --- tests/test_wallets.py | 71 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/tests/test_wallets.py b/tests/test_wallets.py index a7f83ebf0..3e8c5bc45 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -7,12 +7,14 @@ from sqlalchemy import select from freqtrade.constants import UNLIMITED_STAKE_AMOUNT from freqtrade.exceptions import DependencyException -from freqtrade.persistence import Trade +from freqtrade.persistence import Trade, WalletHistory +from freqtrade.wallets import PositionWallet, Wallet from tests.conftest import ( EXMS, create_mock_trades, create_mock_trades_usdt, get_patched_freqtradebot, + log_has_re, patch_wallet, ) @@ -607,3 +609,70 @@ def test_dry_run_wallet_initialization(mocker, default_conf_usdt, config, wallet pytest.approx(freqtrade.wallets._wallets[stake_currency].free) == wallets[stake_currency]["free"] - 100.0 ) + + +@pytest.mark.usefixtures("init_persistence") +def test_record_wallet_state_stores_wallet_history(mocker, default_conf): + freqtrade = get_patched_freqtradebot(mocker, default_conf) + stake_currency = default_conf["stake_currency"] + freqtrade.wallets._wallets = { + stake_currency: Wallet(stake_currency, free=1.0, used=0.5, total=1.5), + "ETH": Wallet("ETH", free=2.0, used=1.0, total=3.0), + } + freqtrade.wallets._positions = { + "ETH/BTC": PositionWallet( + symbol="ETH/BTC", + position=0.8, + collateral=1.0, + leverage=3.0, + side="long", + ) + } + + conversion_rates = {stake_currency: 1.0, "ETH": 0.5, "ETH/BTC": 2500.0} + mocker.patch.object( + freqtrade.exchange, + "get_conversion_rate", + side_effect=lambda currency, _: conversion_rates.get(currency, 1.0), + ) + + freqtrade.wallets.record_wallet_state() + + wallet_entries = WalletHistory.session.query(WalletHistory).all() + assert len(wallet_entries) == 3 + + records_by_currency = {entry.currency: entry for entry in wallet_entries} + assert records_by_currency[stake_currency].balance == 1.5 + assert records_by_currency[stake_currency].price == 1.0 + assert records_by_currency["ETH"].price == 0.5 + assert records_by_currency["ETH/BTC"].balance == 0.8 + assert records_by_currency["ETH/BTC"].price == 2500.0 + + +@pytest.mark.usefixtures("init_persistence") +def test_record_wallet_state_stores_wallet_history_error(mocker, default_conf, caplog): + freqtrade = get_patched_freqtradebot(mocker, default_conf) + stake_currency = default_conf["stake_currency"] + freqtrade.wallets._wallets = { + stake_currency: Wallet(stake_currency, free=1.0, used=0.5, total=1.5), + "ETH": Wallet("ETH", free=2.0, used=1.0, total=3.0), + } + freqtrade.wallets._positions = { + "ETH/BTC": PositionWallet( + symbol="ETH/BTC", + position=0.8, + collateral=1.0, + leverage=3.0, + side="long", + ) + } + + # Mock bulk_save_objects to raise an exception + mocker.patch.object( + WalletHistory.session, "bulk_save_objects", side_effect=Exception("DB Error") + ) + freqtrade.wallets.record_wallet_state() + + assert log_has_re(r"Error saving wallet balance records: .*", caplog) + wallet_entries = WalletHistory.session.query(WalletHistory).all() + assert len(wallet_entries) == 0 From a88dc8839df22822116ac25c0f5167099cd975e2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 6 Jan 2026 10:46:21 +0100 Subject: [PATCH 091/315] chore: simplify imports --- freqtrade/data/btanalysis/trade_parallelism.py | 2 +- freqtrade/wallets.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index 1ffa14af2..82cc043b1 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -5,7 +5,7 @@ import numpy as np import pandas as pd from freqtrade.constants import IntOrInf -from freqtrade.exchange.exchange_utils_timeframe import ( +from freqtrade.exchange import ( timeframe_to_prev_date, timeframe_to_resample_freq, ) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 7f858735b..37d7efd99 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -11,7 +11,7 @@ from freqtrade.exceptions import DependencyException from freqtrade.exchange import Exchange from freqtrade.misc import safe_value_fallback from freqtrade.persistence import LocalTrade, Trade, WalletHistory -from freqtrade.util.datetime_helpers import dt_floor_day, dt_now +from freqtrade.util import dt_floor_day, dt_now logger = logging.getLogger(__name__) From 00d39bbb80621af789739b743cef7b763471ce18 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 6 Jan 2026 11:35:06 +0100 Subject: [PATCH 092/315] feat: add leverage column to wallet history --- freqtrade/persistence/wallet_history.py | 3 ++- freqtrade/wallets.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/persistence/wallet_history.py b/freqtrade/persistence/wallet_history.py index aa72ea3ef..0e384730c 100644 --- a/freqtrade/persistence/wallet_history.py +++ b/freqtrade/persistence/wallet_history.py @@ -20,6 +20,7 @@ class WalletHistory(ModelBase): currency: Mapped[str] = mapped_column(String(25), nullable=False) price: Mapped[float] = mapped_column(Float, nullable=True) balance: Mapped[float] = mapped_column(Float, nullable=False) + leverage: Mapped[float] = mapped_column(Float, nullable=False, default=1.0) __table_args__ = ( # Ensure one record per currency per day @@ -29,5 +30,5 @@ class WalletHistory(ModelBase): def __repr__(self) -> str: return ( f"WalletHistory(timestamp={self.timestamp}, currency={self.currency}, " - f"price={self.price}, balance={self.balance})" + f"price={self.price}, balance={self.balance}, leverage={self.leverage})" ) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 37d7efd99..25dd27fb8 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -465,16 +465,19 @@ class Wallets: currency=wallet.currency, price=price, balance=wallet.total, + leverage=1.0, ) wallet_records.append(wallet_record) for position in self.get_all_positions().values(): - price = self._exchange.get_conversion_rate(position.symbol, self._stake_currency) + base = self._exchange.get_pair_base_currency(position.symbol) + price = self._exchange.get_conversion_rate(base, self._stake_currency) position_record = WalletHistory( timestamp=timestamp, currency=position.symbol, price=price, balance=position.position, + leverage=position.leverage or 1.0, ) wallet_records.append(position_record) try: From b69a042f5be2ab847de479d3860559c7dcb3ada7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 6 Jan 2026 11:35:43 +0100 Subject: [PATCH 093/315] feat: update wallet migration to keep leverage --- freqtrade/data/btanalysis/trade_parallelism.py | 5 ++++- freqtrade/util/migrations/migrate_wallet_history.py | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index 82cc043b1..f69cb4e80 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -93,13 +93,16 @@ def balance_distribution_over_time( min_date_res = timeframe_to_prev_date(timeframe, min_date) max_date_res = timeframe_to_prev_date(timeframe, max_date) index = pd.date_range(min_date_res, max_date_res, freq=timeframe_to_resample_freq(timeframe)) - df = pd.DataFrame(index=index) + pairs_lev = [f"{pair}_leverage" for pair in pairlist] + df = pd.DataFrame(index=index, columns=[stake_currency] + pairlist + pairs_lev, dtype=float) df[stake_currency] = float(start_balance) df[pairlist] = 0.0 + df[pairs_lev] = np.nan for trade in trades.sort_values(by=["open_date"]).itertuples(): end_date = trade.close_date if trade.close_date is not pd.NaT else None # Exclude open orders - these won't have order_filled_timestamp set. orders = [o for o in trade.orders if o["order_filled_timestamp"]] + df.loc[trade.open_date : end_date, f"{trade.pair}_leverage"] = trade.leverage for order in sorted(orders, key=lambda x: x["order_filled_timestamp"]): filled_at = pd.Timestamp(dt_from_ts(order["order_filled_timestamp"])) real_amount = order.get("filled", order["amount"]) / trade.leverage diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 9693dfd7d..a03ac8c70 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -81,6 +81,9 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance # Assume the first column is the index (date) stake_idx = balance_dist.columns.get_loc(stake_currency) pair_balance_idx = {pair: balance_dist.columns.get_loc(pair) + 1 for pair in pairlist_valid} + pair_leverage_idx = { + pair: balance_dist.columns.get_loc(f"{pair}_leverage") + 1 for pair in pairlist_valid + } pair_price_idx = { pair: balance_dist.columns.get_loc(f"{pair}_open") + 1 for pair in pairlist_valid } @@ -106,6 +109,7 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance for pair in pairlist_valid: base_currency = pair.split("/")[0] balance_value = row[pair_balance_idx[pair]] + leverage_value = row[pair_leverage_idx[pair]] # Only add entry if balance is not empty/NaN if not pd.isna(balance_value) and balance_value > 0: price_value = row[pair_price_idx[pair]] @@ -117,6 +121,7 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance currency=base_currency, price=price, balance=balance_value, + leverage=leverage_value if not pd.isna(leverage_value) else 1.0, ) ) From 0835318b8faa9d6d510704f261b3dc17a60a2237 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 6 Jan 2026 11:50:56 +0100 Subject: [PATCH 094/315] fix: capture correct balance for futures --- freqtrade/wallets.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 25dd27fb8..a520f98a7 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -457,18 +457,7 @@ class Wallets: # Record total balances for all currencies wallet_records = [] - for wallet in self.get_all_balances().values(): - # TODO: exclude minimal balances - price = self._exchange.get_conversion_rate(wallet.currency, self._stake_currency) - wallet_record = WalletHistory( - timestamp=timestamp, - currency=wallet.currency, - price=price, - balance=wallet.total, - leverage=1.0, - ) - wallet_records.append(wallet_record) - + position_collaterals = 0.0 for position in self.get_all_positions().values(): base = self._exchange.get_pair_base_currency(position.symbol) price = self._exchange.get_conversion_rate(base, self._stake_currency) @@ -479,7 +468,21 @@ class Wallets: balance=position.position, leverage=position.leverage or 1.0, ) + position_collaterals += position.collateral wallet_records.append(position_record) + + for wallet in self.get_all_balances().values(): + # TODO: exclude minimal balances? + price = self._exchange.get_conversion_rate(wallet.currency, self._stake_currency) + wallet_record = WalletHistory( + timestamp=timestamp, + currency=wallet.currency, + price=price, + balance=wallet.total + - (position_collaterals if wallet.currency == self._stake_currency else 0), + leverage=1.0, + ) + wallet_records.append(wallet_record) try: WalletHistory.session.bulk_save_objects(wallet_records) WalletHistory.session.commit() From 98493dc9ed63aa0de3ba917ded9cf7d5bc51d107 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 6 Jan 2026 15:51:06 +0100 Subject: [PATCH 095/315] feat: add "bot_managed" to wallet_history --- freqtrade/persistence/wallet_history.py | 1 + freqtrade/wallets.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/freqtrade/persistence/wallet_history.py b/freqtrade/persistence/wallet_history.py index 0e384730c..383e42a1c 100644 --- a/freqtrade/persistence/wallet_history.py +++ b/freqtrade/persistence/wallet_history.py @@ -21,6 +21,7 @@ class WalletHistory(ModelBase): price: Mapped[float] = mapped_column(Float, nullable=True) balance: Mapped[float] = mapped_column(Float, nullable=False) leverage: Mapped[float] = mapped_column(Float, nullable=False, default=1.0) + bot_managed: Mapped[bool] = mapped_column(nullable=False, default=True) __table_args__ = ( # Ensure one record per currency per day diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index a520f98a7..b0bdc37a3 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -458,6 +458,7 @@ class Wallets: # Record total balances for all currencies wallet_records = [] position_collaterals = 0.0 + open_assets: dict[str, Trade] = {t.safe_base_currency: t for t in Trade.get_open_trades()} for position in self.get_all_positions().values(): base = self._exchange.get_pair_base_currency(position.symbol) price = self._exchange.get_conversion_rate(base, self._stake_currency) @@ -467,6 +468,7 @@ class Wallets: price=price, balance=position.position, leverage=position.leverage or 1.0, + bot_managed=base in open_assets, ) position_collaterals += position.collateral wallet_records.append(position_record) @@ -474,6 +476,10 @@ class Wallets: for wallet in self.get_all_balances().values(): # TODO: exclude minimal balances? price = self._exchange.get_conversion_rate(wallet.currency, self._stake_currency) + is_bot_managed = ( + self._stake_currency == wallet.currency or wallet.currency in open_assets + ) + wallet_record = WalletHistory( timestamp=timestamp, currency=wallet.currency, @@ -481,6 +487,7 @@ class Wallets: balance=wallet.total - (position_collaterals if wallet.currency == self._stake_currency else 0), leverage=1.0, + bot_managed=is_bot_managed, ) wallet_records.append(wallet_record) try: From 61a5ab8e1c68397ecd865ef49bb6fb0393989731 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 6 Jan 2026 15:51:41 +0100 Subject: [PATCH 096/315] feat: add bot_managed to wallet-history migration --- freqtrade/util/migrations/migrate_wallet_history.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index a03ac8c70..7278c320d 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -102,6 +102,8 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance currency=stake_currency, price=1.0, # Stake currency price is always 1.0 balance=stake_balance, + leverage=1.0, + bot_managed=True, ) ) @@ -122,6 +124,7 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance price=price, balance=balance_value, leverage=leverage_value if not pd.isna(leverage_value) else 1.0, + bot_managed=True, ) ) From 7b223f3d380d8cce3f02222015a670dc13615195 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 6 Jan 2026 16:07:44 +0100 Subject: [PATCH 097/315] feat: improve balance_history response --- freqtrade/rpc/rpc.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index f9939c604..77faa2e62 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -791,13 +791,15 @@ class RPC: :return: DataFrame with the balance history and the timestamp of the migration """ results = read_sql("wallet_history", con=Trade.session.bind, parse_dates=["timestamp"]) - results.loc[:, "total"] = results["price"] * results["balance"] + results.loc[:, "total"] = results["price"] * results["balance"] / results["leverage"] results = results.rename({"timestamp": "date"}, axis=1) results.loc[:, "__date_ts"] = results.loc[:, "date"].astype("int64") // 1000 // 1000 + # Exclude non-bot managed for now + results = results.loc[results["bot_managed"]] - results = results.groupby(["date", "__date_ts"]).agg({"total": "sum"}).reset_index() + results_final = results.groupby(["date", "__date_ts"]).agg({"total": "sum"}).reset_index() hist = KeyValueStore.get_datetime_value("wallet_history_migration_date") - return results, dt_ts_def(hist, 0) + return results_final, dt_ts_def(hist, 0) def __balance_get_est_stake( self, coin: str, stake_currency: str, amount: float, balance: Wallet From 9891b8332b0170fe80bfd26df54d03ab5cc3d3cd Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 6 Jan 2026 16:10:38 +0100 Subject: [PATCH 098/315] chore: set wallet_migration_date in the correct space --- freqtrade/util/migrations/migrate_wallet_history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 7278c320d..1eb8968eb 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -25,6 +25,7 @@ def migrate_wallet_history(config: Config, exchange: Exchange, starting_balance: _migrate_wallet_history(config, exchange, starting_balance) logger.info("Wallet history migration completed.") KeyValueStore.store_value("wallet_history_migration", 1) + KeyValueStore.store_value("wallet_history_migration_date", dt_now()) def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance: float): @@ -134,7 +135,6 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance # Use bulk_save_objects for better performance WalletHistory.session.bulk_save_objects(wallet_entries) WalletHistory.session.commit() - KeyValueStore.store_value("wallet_history_migration_date", dt_now()) logger.info(f"Successfully created {len(wallet_entries)} wallet balance records") except Exception as e: WalletHistory.session.rollback() From a09036cd6e7ee483e827c220103428471f68c35d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 6 Jan 2026 16:30:18 +0100 Subject: [PATCH 099/315] chore: fix Model naming collision --- freqtrade/rpc/api_server/api_backtest.py | 4 ++-- freqtrade/rpc/api_server/api_schemas.py | 2 +- freqtrade/rpc/api_server/api_trading.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index ab451cfbd..772b33f99 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -30,7 +30,7 @@ from freqtrade.rpc.api_server.api_schemas import ( BacktestMetadataUpdate, BacktestRequest, BacktestResponse, - WalletHistory, + WalletHistoryResponse, ) from freqtrade.rpc.api_server.deps import get_config, verify_strategy from freqtrade.rpc.api_server.webserver_bgwork import ApiBG @@ -366,7 +366,7 @@ def api_get_backtest_market_change(file: str, config=Depends(get_config)): @router.get( "/backtest/history/{file}/{strategy}/wallet", - response_model=WalletHistory, + response_model=WalletHistoryResponse, tags=["webserver", "backtest"], ) def api_get_backtest_wallet(file: str, strategy: str, config=Depends(get_config)): diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 3837afb43..722438ae3 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -679,7 +679,7 @@ class BacktestMarketChange(BaseModel): data: list[list[Any]] -class WalletHistory(BaseModel): +class WalletHistoryResponse(BaseModel): columns: list[str] length: int data: list[list[Any]] diff --git a/freqtrade/rpc/api_server/api_trading.py b/freqtrade/rpc/api_server/api_trading.py index 368de9f79..0bae0eefb 100644 --- a/freqtrade/rpc/api_server/api_trading.py +++ b/freqtrade/rpc/api_server/api_trading.py @@ -31,7 +31,7 @@ from freqtrade.rpc.api_server.api_schemas import ( ResultMsg, Stats, StatusMsg, - WalletHistory, + WalletHistoryResponse, WhitelistResponse, ) from freqtrade.rpc.api_server.deps import get_config, get_rpc @@ -107,7 +107,7 @@ def stats(rpc: RPC = Depends(get_rpc)): @router.get( "/historic_balance", - response_model=WalletHistory, + response_model=WalletHistoryResponse, tags=["info"], ) def api_get_wallet_history(rpc: RPC = Depends(get_rpc)): From 1237bb798cc909702ee16524963e4472e9f64223 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Jan 2026 20:11:57 +0100 Subject: [PATCH 100/315] test: fix backtest api wallets test --- tests/rpc/test_rpc_apiserver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 226abec1c..af5f5e5bf 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -3349,9 +3349,9 @@ def test_api_backtest_wallets(botclient, tmp_path: Path): ftbot.config["user_data_dir"] = tmp_path ftbot.config["runmode"] = RunMode.WEBSERVER - # Nonexisting file + # Nonexisting file - fails "is_file_in_dir" check rc = client_get(client, f"{BASE_URI}/backtest/history/randomFile.json/SampleStrategy/wallet") - assert_response(rc, 404) + assert_response(rc, 400) rc = client_get(client, f"{BASE_URI}/backtest/history/backtest_15/SampleStrategy/wallet") assert_response(rc, 200) From 623991c772e79469e4ae8ed7b02fcdf35fc704b7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Jan 2026 20:18:51 +0100 Subject: [PATCH 101/315] chore: rename variable to price --- freqtrade/wallets.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index b0bdc37a3..817e98edf 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -461,11 +461,11 @@ class Wallets: open_assets: dict[str, Trade] = {t.safe_base_currency: t for t in Trade.get_open_trades()} for position in self.get_all_positions().values(): base = self._exchange.get_pair_base_currency(position.symbol) - price = self._exchange.get_conversion_rate(base, self._stake_currency) + rate = self._exchange.get_conversion_rate(base, self._stake_currency) position_record = WalletHistory( timestamp=timestamp, currency=position.symbol, - price=price, + price=rate, balance=position.position, leverage=position.leverage or 1.0, bot_managed=base in open_assets, @@ -475,7 +475,7 @@ class Wallets: for wallet in self.get_all_balances().values(): # TODO: exclude minimal balances? - price = self._exchange.get_conversion_rate(wallet.currency, self._stake_currency) + rate = self._exchange.get_conversion_rate(wallet.currency, self._stake_currency) is_bot_managed = ( self._stake_currency == wallet.currency or wallet.currency in open_assets ) @@ -483,7 +483,7 @@ class Wallets: wallet_record = WalletHistory( timestamp=timestamp, currency=wallet.currency, - price=price, + price=rate, balance=wallet.total - (position_collaterals if wallet.currency == self._stake_currency else 0), leverage=1.0, From f7ddf46b3271ea11d41680219e9cb445ac38aac1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 12 Jan 2026 20:28:47 +0100 Subject: [PATCH 102/315] refactor: rename WalletHistory fieldname from price to rate --- .../optimize/optimize_reports/optimize_reports.py | 4 ++-- freqtrade/persistence/wallet_history.py | 4 ++-- freqtrade/rpc/rpc.py | 2 +- freqtrade/util/migrations/migrate_wallet_history.py | 10 +++++----- freqtrade/wallets.py | 4 ++-- tests/test_wallets.py | 6 +++--- tests/util/test_historic_wallets_migration.py | 6 +++--- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index d260916c1..ba0b4304a 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -39,7 +39,7 @@ def convert_bt_wallet_collection(wallet_captures: list[tuple]) -> DataFrame: return DataFrame() return DataFrame( wallet_captures, - columns=["date", "currency", "price", "balance"], + columns=["date", "currency", "rate", "balance"], ) @@ -47,7 +47,7 @@ def generate_wallet_stats(wallet_df: DataFrame, stake_currency: str) -> dict[str """Generate wallet statistics from the wallet DataFrame.""" if wallet_df is None or wallet_df.empty: return {} - wallet_df.loc[:, "total"] = wallet_df["price"] * wallet_df["balance"] + wallet_df.loc[:, "total"] = wallet_df["rate"] * wallet_df["balance"] # Group by date to get total wallet value at each timestamp wallet = wallet_df.groupby("date")["total"].sum().reset_index() start_balance = wallet.iloc[0]["total"] diff --git a/freqtrade/persistence/wallet_history.py b/freqtrade/persistence/wallet_history.py index 383e42a1c..de9ccd986 100644 --- a/freqtrade/persistence/wallet_history.py +++ b/freqtrade/persistence/wallet_history.py @@ -18,7 +18,7 @@ class WalletHistory(ModelBase): id: Mapped[int] = mapped_column(Integer, primary_key=True) timestamp: Mapped[datetime] = mapped_column(DateTime, nullable=False, index=True) currency: Mapped[str] = mapped_column(String(25), nullable=False) - price: Mapped[float] = mapped_column(Float, nullable=True) + rate: Mapped[float] = mapped_column(Float, nullable=True) balance: Mapped[float] = mapped_column(Float, nullable=False) leverage: Mapped[float] = mapped_column(Float, nullable=False, default=1.0) bot_managed: Mapped[bool] = mapped_column(nullable=False, default=True) @@ -31,5 +31,5 @@ class WalletHistory(ModelBase): def __repr__(self) -> str: return ( f"WalletHistory(timestamp={self.timestamp}, currency={self.currency}, " - f"price={self.price}, balance={self.balance}, leverage={self.leverage})" + f"rate={self.rate}, balance={self.balance}, leverage={self.leverage})" ) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 77faa2e62..b9f783005 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -791,7 +791,7 @@ class RPC: :return: DataFrame with the balance history and the timestamp of the migration """ results = read_sql("wallet_history", con=Trade.session.bind, parse_dates=["timestamp"]) - results.loc[:, "total"] = results["price"] * results["balance"] / results["leverage"] + results.loc[:, "total"] = results["rate"] * results["balance"] / results["leverage"] results = results.rename({"timestamp": "date"}, axis=1) results.loc[:, "__date_ts"] = results.loc[:, "date"].astype("int64") // 1000 // 1000 # Exclude non-bot managed for now diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 1eb8968eb..45d4dd9b3 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -85,7 +85,7 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance pair_leverage_idx = { pair: balance_dist.columns.get_loc(f"{pair}_leverage") + 1 for pair in pairlist_valid } - pair_price_idx = { + pair_rate_idx = { pair: balance_dist.columns.get_loc(f"{pair}_open") + 1 for pair in pairlist_valid } @@ -101,7 +101,7 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance WalletHistory( timestamp=date, currency=stake_currency, - price=1.0, # Stake currency price is always 1.0 + rate=1.0, # Stake currency price is always 1.0 balance=stake_balance, leverage=1.0, bot_managed=True, @@ -115,14 +115,14 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance leverage_value = row[pair_leverage_idx[pair]] # Only add entry if balance is not empty/NaN if not pd.isna(balance_value) and balance_value > 0: - price_value = row[pair_price_idx[pair]] - price = price_value if not pd.isna(price_value) else None + rate_value = row[pair_rate_idx[pair]] + rate = rate_value if not pd.isna(rate_value) else None wallet_entries.append( WalletHistory( timestamp=date, currency=base_currency, - price=price, + rate=rate, balance=balance_value, leverage=leverage_value if not pd.isna(leverage_value) else 1.0, bot_managed=True, diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 817e98edf..b37348baf 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -465,7 +465,7 @@ class Wallets: position_record = WalletHistory( timestamp=timestamp, currency=position.symbol, - price=rate, + rate=rate, balance=position.position, leverage=position.leverage or 1.0, bot_managed=base in open_assets, @@ -483,7 +483,7 @@ class Wallets: wallet_record = WalletHistory( timestamp=timestamp, currency=wallet.currency, - price=rate, + rate=rate, balance=wallet.total - (position_collaterals if wallet.currency == self._stake_currency else 0), leverage=1.0, diff --git a/tests/test_wallets.py b/tests/test_wallets.py index 3e8c5bc45..357c67baf 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -643,10 +643,10 @@ def test_record_wallet_state_stores_wallet_history(mocker, default_conf): records_by_currency = {entry.currency: entry for entry in wallet_entries} assert records_by_currency[stake_currency].balance == 1.5 - assert records_by_currency[stake_currency].price == 1.0 - assert records_by_currency["ETH"].price == 0.5 + assert records_by_currency[stake_currency].rate == 1.0 + assert records_by_currency["ETH"].rate == 0.5 assert records_by_currency["ETH/BTC"].balance == 0.8 - assert records_by_currency["ETH/BTC"].price == 2500.0 + assert records_by_currency["ETH/BTC"].rate == 2500.0 @pytest.mark.usefixtures("init_persistence") diff --git a/tests/util/test_historic_wallets_migration.py b/tests/util/test_historic_wallets_migration.py index e5f40bb8f..2de03a7a3 100644 --- a/tests/util/test_historic_wallets_migration.py +++ b/tests/util/test_historic_wallets_migration.py @@ -232,14 +232,14 @@ def test_migrate_wallet_history_with_multiple_pairs(default_conf_usdt, fee, time # Stake currency should have price = 1.0 for entry in usdt_entries: - assert entry.price == 1.0 + assert entry.rate == 1.0 eth_entries = [e for e in wallet_entries if e.currency == "ETH"] btc_entries = [e for e in wallet_entries if e.currency == "BTC"] assert len(eth_entries) == 4 assert len(btc_entries) == 2 - assert all(entry.price and entry.price > 1400 and entry.price < 1600 for entry in eth_entries) - assert all(entry.price and entry.price > 29000 and entry.price < 31000 for entry in btc_entries) + assert all(entry.rate and entry.rate > 1400 and entry.rate < 1600 for entry in eth_entries) + assert all(entry.rate and entry.rate > 29000 and entry.rate < 31000 for entry in btc_entries) assert all(entry.balance == 10 for entry in btc_entries) From f12534958785be05dd8d00ba09479816eeb6b9c9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 13 Jan 2026 21:13:12 +0100 Subject: [PATCH 103/315] feat: add additional columns to better cover futures --- freqtrade/persistence/wallet_history.py | 17 ++++++++++++++++- freqtrade/rpc/rpc.py | 6 ++++-- freqtrade/wallets.py | 24 ++++++++++++++++++++++-- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/freqtrade/persistence/wallet_history.py b/freqtrade/persistence/wallet_history.py index de9ccd986..2aef7000d 100644 --- a/freqtrade/persistence/wallet_history.py +++ b/freqtrade/persistence/wallet_history.py @@ -18,9 +18,23 @@ class WalletHistory(ModelBase): id: Mapped[int] = mapped_column(Integer, primary_key=True) timestamp: Mapped[datetime] = mapped_column(DateTime, nullable=False, index=True) currency: Mapped[str] = mapped_column(String(25), nullable=False) + # Rate: price of 1 unit of `currency` quoted in `quote_currency`. + # e.g., USDT/ETH -> USDT per ETH rate: Mapped[float] = mapped_column(Float, nullable=True) + # Quote currency for rate/total fields (e.g., 'USDT') + quote_currency: Mapped[str] = mapped_column(String(25), nullable=False) + + # Balance in `currency` units balance: Mapped[float] = mapped_column(Float, nullable=False) + + # Canonical total wallet equity/value denominated in `quote_currency` (if available) + # For futures positions, collateral + PnL is used to compute this value. + total_quote: Mapped[float] = mapped_column(Float, nullable=True) + # Total position value in `quote_currency` - including leverage + total_position_value: Mapped[float] = mapped_column(Float, nullable=True) + collateral: Mapped[float] = mapped_column(Float, nullable=True) leverage: Mapped[float] = mapped_column(Float, nullable=False, default=1.0) + bot_managed: Mapped[bool] = mapped_column(nullable=False, default=True) __table_args__ = ( @@ -31,5 +45,6 @@ class WalletHistory(ModelBase): def __repr__(self) -> str: return ( f"WalletHistory(timestamp={self.timestamp}, currency={self.currency}, " - f"rate={self.rate}, balance={self.balance}, leverage={self.leverage})" + f"rate={self.rate}, total_quote={self.total_quote}, " + f"balance={self.balance}, leverage={self.leverage})" ) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index b9f783005..ecb17128a 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -791,13 +791,15 @@ class RPC: :return: DataFrame with the balance history and the timestamp of the migration """ results = read_sql("wallet_history", con=Trade.session.bind, parse_dates=["timestamp"]) - results.loc[:, "total"] = results["rate"] * results["balance"] / results["leverage"] + results = results.rename({"timestamp": "date"}, axis=1) results.loc[:, "__date_ts"] = results.loc[:, "date"].astype("int64") // 1000 // 1000 # Exclude non-bot managed for now results = results.loc[results["bot_managed"]] - results_final = results.groupby(["date", "__date_ts"]).agg({"total": "sum"}).reset_index() + results_final = ( + results.groupby(["date", "__date_ts"]).agg({"total_quote": "sum"}).reset_index() + ) hist = KeyValueStore.get_datetime_value("wallet_history_migration_date") return results_final, dt_ts_def(hist, 0) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index b37348baf..f58baebe7 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -462,11 +462,26 @@ class Wallets: for position in self.get_all_positions().values(): base = self._exchange.get_pair_base_currency(position.symbol) rate = self._exchange.get_conversion_rate(base, self._stake_currency) + total_quote = None + if rate: + total_quote = ( + rate * position.position - position.collateral * (position.leverage - 1) + if position.side == "long" + else ( + position.collateral + - (rate * position.position - position.collateral * position.leverage) + ) + ) + position_record = WalletHistory( timestamp=timestamp, currency=position.symbol, + quote_currency=self._stake_currency, rate=rate, balance=position.position, + total_quote=total_quote, + total_position_value=rate * position.position if rate else None, + collateral=position.collateral, leverage=position.leverage or 1.0, bot_managed=base in open_assets, ) @@ -479,14 +494,19 @@ class Wallets: is_bot_managed = ( self._stake_currency == wallet.currency or wallet.currency in open_assets ) + balance = wallet.total - ( + position_collaterals if wallet.currency == self._stake_currency else 0 + ) + total_quote = rate * balance if rate else None wallet_record = WalletHistory( timestamp=timestamp, currency=wallet.currency, + quote_currency=self._stake_currency, rate=rate, - balance=wallet.total - - (position_collaterals if wallet.currency == self._stake_currency else 0), + balance=balance, leverage=1.0, + total_quote=total_quote, bot_managed=is_bot_managed, ) wallet_records.append(wallet_record) From 21ac7d196901f27a1c47f5208cc5633ebb1fc66c Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 16 Jan 2026 06:14:04 +0100 Subject: [PATCH 104/315] refactor: use shorter variable name --- freqtrade/wallets.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index f58baebe7..231282437 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -459,33 +459,30 @@ class Wallets: wallet_records = [] position_collaterals = 0.0 open_assets: dict[str, Trade] = {t.safe_base_currency: t for t in Trade.get_open_trades()} - for position in self.get_all_positions().values(): - base = self._exchange.get_pair_base_currency(position.symbol) + for pos in self.get_all_positions().values(): + base = self._exchange.get_pair_base_currency(pos.symbol) rate = self._exchange.get_conversion_rate(base, self._stake_currency) total_quote = None if rate: total_quote = ( - rate * position.position - position.collateral * (position.leverage - 1) - if position.side == "long" - else ( - position.collateral - - (rate * position.position - position.collateral * position.leverage) - ) + rate * pos.position - pos.collateral * (pos.leverage - 1) + if pos.side == "long" + else (pos.collateral - (rate * pos.position - pos.collateral * pos.leverage)) ) position_record = WalletHistory( timestamp=timestamp, - currency=position.symbol, + currency=pos.symbol, quote_currency=self._stake_currency, rate=rate, - balance=position.position, + balance=pos.position, total_quote=total_quote, - total_position_value=rate * position.position if rate else None, - collateral=position.collateral, - leverage=position.leverage or 1.0, + total_position_value=rate * pos.position if rate else None, + collateral=pos.collateral, + leverage=pos.leverage or 1.0, bot_managed=base in open_assets, ) - position_collaterals += position.collateral + position_collaterals += pos.collateral wallet_records.append(position_record) for wallet in self.get_all_balances().values(): From 05bbc84bf2c72b296c95806552e967e2c9d65677 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 16 Jan 2026 06:16:40 +0100 Subject: [PATCH 105/315] fix: use correct formula for wallet capture --- freqtrade/wallets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 231282437..6cd8f9e0a 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -464,10 +464,11 @@ class Wallets: rate = self._exchange.get_conversion_rate(base, self._stake_currency) total_quote = None if rate: + # Same formula than in rpc's _rpc_balance total_quote = ( rate * pos.position - pos.collateral * (pos.leverage - 1) if pos.side == "long" - else (pos.collateral - (rate * pos.position - pos.collateral * pos.leverage)) + else (pos.collateral + (rate * pos.position - pos.collateral * pos.leverage)) ) position_record = WalletHistory( From 4cac7247092c4c11e8e3f770a0041faa9a670f4b Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 16 Jan 2026 06:28:34 +0100 Subject: [PATCH 106/315] test: update wallet capture test --- tests/test_wallets.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/tests/test_wallets.py b/tests/test_wallets.py index 357c67baf..b14a5e602 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -612,16 +612,16 @@ def test_dry_run_wallet_initialization(mocker, default_conf_usdt, config, wallet @pytest.mark.usefixtures("init_persistence") -def test_record_wallet_state_stores_wallet_history(mocker, default_conf): - freqtrade = get_patched_freqtradebot(mocker, default_conf) - stake_currency = default_conf["stake_currency"] +def test_record_wallet_state_stores_wallet_history(mocker, default_conf_usdt): + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + stake_currency = default_conf_usdt["stake_currency"] freqtrade.wallets._wallets = { - stake_currency: Wallet(stake_currency, free=1.0, used=0.5, total=1.5), - "ETH": Wallet("ETH", free=2.0, used=1.0, total=3.0), + stake_currency: Wallet(stake_currency, free=100.0, used=50, total=150), + "BTC": Wallet("BTC", free=2.0, used=1.0, total=3.0), } freqtrade.wallets._positions = { - "ETH/BTC": PositionWallet( - symbol="ETH/BTC", + "ETH/USDT:USDT": PositionWallet( + symbol="ETH/USDT:USDT", position=0.8, collateral=1.0, leverage=3.0, @@ -629,12 +629,18 @@ def test_record_wallet_state_stores_wallet_history(mocker, default_conf): ) } - conversion_rates = {stake_currency: 1.0, "ETH": 0.5, "ETH/BTC": 2500.0} + conversion_rates = {stake_currency: 1.0, "BTC": 70000, "ETH": 2500.1} mocker.patch.object( freqtrade.exchange, "get_conversion_rate", side_effect=lambda currency, _: conversion_rates.get(currency, 1.0), ) + mocker.patch( + "freqtrade.persistence.trade_model.Trade.get_open_trades", + return_value=[ + MagicMock(pair="ETH/USDT:USDT", safe_base_currency="ETH"), + ], + ) freqtrade.wallets.record_wallet_state() @@ -642,11 +648,14 @@ def test_record_wallet_state_stores_wallet_history(mocker, default_conf): assert len(wallet_entries) == 3 records_by_currency = {entry.currency: entry for entry in wallet_entries} - assert records_by_currency[stake_currency].balance == 1.5 + assert records_by_currency[stake_currency].balance == 149 assert records_by_currency[stake_currency].rate == 1.0 - assert records_by_currency["ETH"].rate == 0.5 - assert records_by_currency["ETH/BTC"].balance == 0.8 - assert records_by_currency["ETH/BTC"].rate == 2500.0 + assert records_by_currency["BTC"].rate == 70000 + assert records_by_currency["BTC"].balance == 3 + assert not records_by_currency["BTC"].bot_managed + assert records_by_currency["ETH/USDT:USDT"].balance == 0.8 + assert records_by_currency["ETH/USDT:USDT"].rate == 2500.1 + assert records_by_currency["ETH/USDT:USDT"].bot_managed is True @pytest.mark.usefixtures("init_persistence") From b4961a2cb7d63cd5423bacfbf4ce4abcff6c5abd Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 16 Jan 2026 06:29:59 +0100 Subject: [PATCH 107/315] fix: add quote_currency to wallet migration --- freqtrade/util/migrations/migrate_wallet_history.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 45d4dd9b3..68caaf6ec 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -103,6 +103,7 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance currency=stake_currency, rate=1.0, # Stake currency price is always 1.0 balance=stake_balance, + quote_currency=stake_currency, leverage=1.0, bot_managed=True, ) @@ -123,6 +124,7 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance timestamp=date, currency=base_currency, rate=rate, + quote_currency=stake_currency, balance=balance_value, leverage=leverage_value if not pd.isna(leverage_value) else 1.0, bot_managed=True, From 50fd6d152ed388cc955db85ee1f3890b5e0dc37a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 17 Jan 2026 16:53:10 +0100 Subject: [PATCH 108/315] fix: use correct formula for shorts --- freqtrade/wallets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 6cd8f9e0a..41ce229c6 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -468,7 +468,7 @@ class Wallets: total_quote = ( rate * pos.position - pos.collateral * (pos.leverage - 1) if pos.side == "long" - else (pos.collateral + (rate * pos.position - pos.collateral * pos.leverage)) + else pos.collateral * (1 + pos.leverage) - rate * pos.position ) position_record = WalletHistory( From 5a847665ff082fbc5ec7112c763f6fc066a4d940 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Jan 2026 14:08:08 +0100 Subject: [PATCH 109/315] fix: add total_quote to migration --- freqtrade/util/migrations/migrate_wallet_history.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 68caaf6ec..48cf9765e 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -103,6 +103,7 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance currency=stake_currency, rate=1.0, # Stake currency price is always 1.0 balance=stake_balance, + total_quote=stake_balance, quote_currency=stake_currency, leverage=1.0, bot_managed=True, @@ -126,8 +127,11 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance rate=rate, quote_currency=stake_currency, balance=balance_value, + total_quote=balance_value * rate if rate else None, leverage=leverage_value if not pd.isna(leverage_value) else 1.0, bot_managed=True, + # total_position_value=total_position_value, + # collateral=collateral, ) ) From 09ddef37167f7d46809c9d4e7089a972c2981a20 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Jan 2026 14:28:22 +0100 Subject: [PATCH 110/315] chore: improved variable naming --- freqtrade/util/migrations/migrate_wallet_history.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 48cf9765e..75a7ff629 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -113,10 +113,10 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance # Add entries for each trading pair for pair in pairlist_valid: base_currency = pair.split("/")[0] - balance_value = row[pair_balance_idx[pair]] - leverage_value = row[pair_leverage_idx[pair]] + balance = row[pair_balance_idx[pair]] + leverage = row[pair_leverage_idx[pair]] # Only add entry if balance is not empty/NaN - if not pd.isna(balance_value) and balance_value > 0: + if not pd.isna(balance) and balance > 0: rate_value = row[pair_rate_idx[pair]] rate = rate_value if not pd.isna(rate_value) else None @@ -126,9 +126,9 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance currency=base_currency, rate=rate, quote_currency=stake_currency, - balance=balance_value, - total_quote=balance_value * rate if rate else None, - leverage=leverage_value if not pd.isna(leverage_value) else 1.0, + balance=balance, + total_quote=balance * rate if rate else None, + leverage=leverage if not pd.isna(leverage) else 1.0, bot_managed=True, # total_position_value=total_position_value, # collateral=collateral, From 803b4cae788f99a97cd3b3dce0775e9f0e25f25e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 18 Jan 2026 14:32:37 +0100 Subject: [PATCH 111/315] chore: improved docstring --- freqtrade/data/btanalysis/trade_parallelism.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index f69cb4e80..15d6d2138 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -78,6 +78,10 @@ def balance_distribution_over_time( """ Return a dataframe with stake_currency and the pairlist as columns Each column will contain the amount of the currency at the given time + Columns added are: + - stake_currency: amount of stake currency + - : amount of base currency in the pair + - _leverage: leverage used for the pair at the time (NaN if no open trade) :param trades: Trades Dataframe - can be loaded from backtest, or created via trade_list_to_dataframe :param timeframe: Frequency to use for the resulting dataframe From e4eee1aa1b6ff85ee4d8ad65ba002380f41fdac5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Jan 2026 20:17:15 +0100 Subject: [PATCH 112/315] feat: add short fields to balance_distribution --- .../data/btanalysis/trade_parallelism.py | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index 15d6d2138..9e023a6b5 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -82,6 +82,8 @@ def balance_distribution_over_time( - stake_currency: amount of stake currency - : amount of base currency in the pair - _leverage: leverage used for the pair at the time (NaN if no open trade) + - _is_short: 1 if the open trade is short, 0 if long (NaN if no open trade) + - _collateral: amount of stake currency used as collateral for open trades :param trades: Trades Dataframe - can be loaded from backtest, or created via trade_list_to_dataframe :param timeframe: Frequency to use for the resulting dataframe @@ -98,26 +100,38 @@ def balance_distribution_over_time( max_date_res = timeframe_to_prev_date(timeframe, max_date) index = pd.date_range(min_date_res, max_date_res, freq=timeframe_to_resample_freq(timeframe)) pairs_lev = [f"{pair}_leverage" for pair in pairlist] - df = pd.DataFrame(index=index, columns=[stake_currency] + pairlist + pairs_lev, dtype=float) + pairs_is_short = [f"{pair}_is_short" for pair in pairlist] + pairs_collateral = [f"{pair}_collateral" for pair in pairlist] + pairs_lev += pairs_is_short + + df = pd.DataFrame( + index=index, columns=[stake_currency] + pairlist + pairs_lev + pairs_collateral, dtype=float + ) + # Initialize variables to starting values df[stake_currency] = float(start_balance) - df[pairlist] = 0.0 + df[pairlist + pairs_collateral] = 0.0 df[pairs_lev] = np.nan + for trade in trades.sort_values(by=["open_date"]).itertuples(): + pair = trade.pair end_date = trade.close_date if trade.close_date is not pd.NaT else None # Exclude open orders - these won't have order_filled_timestamp set. orders = [o for o in trade.orders if o["order_filled_timestamp"]] - df.loc[trade.open_date : end_date, f"{trade.pair}_leverage"] = trade.leverage + df.loc[trade.open_date : end_date, f"{pair}_leverage"] = trade.leverage + df.loc[trade.open_date : end_date, f"{pair}_is_short"] = 1 if trade.is_short else 0 for order in sorted(orders, key=lambda x: x["order_filled_timestamp"]): filled_at = pd.Timestamp(dt_from_ts(order["order_filled_timestamp"])) - real_amount = order.get("filled", order["amount"]) / trade.leverage + real_amount = order.get("filled", order["amount"]) stake = order["safe_price"] * real_amount if order["ft_is_entry"]: fee = stake * trade.fee_open - df.loc[filled_at:end_date, trade.pair] += real_amount + df.loc[filled_at:end_date, pair] += real_amount + df.loc[filled_at:end_date, f"{pair}_collateral"] += stake / trade.leverage df.loc[filled_at:, stake_currency] -= stake + fee else: fee = stake * trade.fee_close - df.loc[filled_at:end_date, trade.pair] -= real_amount + df.loc[filled_at:end_date, pair] -= real_amount + df.loc[filled_at:end_date, f"{pair}_collateral"] -= stake / trade.leverage df.loc[filled_at:, stake_currency] += stake - fee # Round to avoid floating point issues From 5d2a7d218771a706100c23b4f203906d151d0910 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 25 Jan 2026 20:22:33 +0100 Subject: [PATCH 113/315] feat: wallet-migration for futures trades --- .../util/migrations/migrate_wallet_history.py | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 75a7ff629..82fb2ab91 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -35,6 +35,7 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance return pairlist = list(trade_df["pair"].unique()) timeframe = "1d" + is_futures = config["trading_mode"] == "futures" stake_currency = config["stake_currency"] min_date = timeframe_to_prev_date(timeframe, KeyValueStore.get_datetime_value("bot_start_time")) balance_dist = balance_distribution_over_time( @@ -85,6 +86,12 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance pair_leverage_idx = { pair: balance_dist.columns.get_loc(f"{pair}_leverage") + 1 for pair in pairlist_valid } + pair_collateral_idx = { + pair: balance_dist.columns.get_loc(f"{pair}_collateral") + 1 for pair in pairlist_valid + } + pair_is_short_idx = { + pair: balance_dist.columns.get_loc(f"{pair}_is_short") + 1 for pair in pairlist_valid + } pair_rate_idx = { pair: balance_dist.columns.get_loc(f"{pair}_open") + 1 for pair in pairlist_valid } @@ -120,17 +127,29 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance rate_value = row[pair_rate_idx[pair]] rate = rate_value if not pd.isna(rate_value) else None + total_quote = balance * rate if rate else None + collateral: float | None = None + if is_futures: + collateral = row[pair_collateral_idx[pair]] + is_short = row[pair_is_short_idx[pair]] + if collateral is not None and not pd.isna(collateral): + # Same formula than in rpc's _rpc_balance + total_quote = ( + (rate * balance - collateral * (leverage - 1)) + if is_short == 0 + else (collateral * (1 + leverage) - rate * balance) + ) wallet_entries.append( WalletHistory( timestamp=date, currency=base_currency, - rate=rate, quote_currency=stake_currency, + rate=rate, balance=balance, - total_quote=balance * rate if rate else None, + total_quote=total_quote, leverage=leverage if not pd.isna(leverage) else 1.0, bot_managed=True, - # total_position_value=total_position_value, + total_position_value=balance * rate if is_futures and rate else None, # collateral=collateral, ) ) From fa48910e962a386b9d574d631ce2eb9192a3b5d4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 27 Jan 2026 07:17:51 +0100 Subject: [PATCH 114/315] feat: improved dataframe handling --- freqtrade/util/migrations/migrate_wallet_history.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 82fb2ab91..ec0776e4d 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -72,9 +72,14 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance merged = pd.concat(dfs, axis=1) balance_dist = balance_dist.join(merged, how="left") + df_value = pd.DataFrame( + index=balance_dist.index, columns=[f"{p}_value" for p in pairlist_valid], dtype=float + ) for p in pairlist_valid: - balance_dist[f"{p}_value"] = balance_dist[f"{p}_open"] * balance_dist[p] + df_value[f"{p}_value"] = balance_dist[f"{p}_open"] * balance_dist[p] + balance_dist = pd.concat([balance_dist, df_value], axis=1) + # Aggregate total value at each point in time balance_dist["total_value"] = balance_dist[ [f"{p}_value" for p in pairlist_valid] + [stake_currency] ].sum(axis=1) From ab2e0e6d6f1dbd9fb775d6c1ea151b42b7ad290b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 27 Jan 2026 07:19:45 +0100 Subject: [PATCH 115/315] chore: improved logging for clarity on startup wait --- freqtrade/util/migrations/migrate_wallet_history.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index ec0776e4d..b8cc5cfda 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -49,12 +49,16 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance ) pairlist_valid = [p for p in pairlist if p in exchange.markets] + logger.info("Wallet History migration: Fetching OHLCV data ...") data = exchange.refresh_latest_ohlcv( [(p, timeframe, config["candle_type_def"]) for p in pairlist_valid], since_ms=dt_ts(min_date), cache=False, drop_incomplete=False, ) + logger.info( + "Wallet History migration: Done fetching OHLCV data for wallet history migration..." + ) dfs = [] # Combine all dataframes into one using the open rate From 5d3776309c1b4c5c4d43cdc9165f21bcd355fee8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 27 Jan 2026 07:23:19 +0100 Subject: [PATCH 116/315] refactor: improve migration code structure --- .../util/migrations/migrate_wallet_history.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index b8cc5cfda..ca9dbae88 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -29,13 +29,19 @@ def migrate_wallet_history(config: Config, exchange: Exchange, starting_balance: def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance: float): + # Prepare balance distribution data with OHLCV rates + balance_dist, pairlist_valid = _prepare_balance_distribution(config, exchange, starting_balance) + + _create_wallet_history_entries(config, balance_dist, pairlist_valid, config["stake_currency"]) + + +def _prepare_balance_distribution(config: Config, exchange: Exchange, starting_balance: float): trade_df = trade_list_to_dataframe(Trade.get_trades_proxy(), minified=False) if trade_df.empty: # no trades, nothing to do return pairlist = list(trade_df["pair"].unique()) timeframe = "1d" - is_futures = config["trading_mode"] == "futures" stake_currency = config["stake_currency"] min_date = timeframe_to_prev_date(timeframe, KeyValueStore.get_datetime_value("bot_start_time")) balance_dist = balance_distribution_over_time( @@ -88,6 +94,16 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance [f"{p}_value" for p in pairlist_valid] + [stake_currency] ].sum(axis=1) + return balance_dist, pairlist_valid + + +def _create_wallet_history_entries( + config: Config, + balance_dist: pd.DataFrame, + pairlist_valid: list[str], + stake_currency: str, +): + is_futures = config["trading_mode"] == "futures" # Precompute column indices for faster tuple-based iteration # Assume the first column is the index (date) stake_idx = balance_dist.columns.get_loc(stake_currency) @@ -104,7 +120,6 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance pair_rate_idx = { pair: balance_dist.columns.get_loc(f"{pair}_open") + 1 for pair in pairlist_valid } - # Convert balance_dist to WalletHistory entries wallet_entries = [] for row in balance_dist.itertuples(index=True, name=None): From 518092a0ad5b39fa16962ecc06fadf27115243a7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 27 Jan 2026 19:11:16 +0100 Subject: [PATCH 117/315] fix: handle error-cases gracefully --- .../util/migrations/migrate_wallet_history.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index ca9dbae88..7c91a9975 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -31,15 +31,19 @@ def migrate_wallet_history(config: Config, exchange: Exchange, starting_balance: def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance: float): # Prepare balance distribution data with OHLCV rates balance_dist, pairlist_valid = _prepare_balance_distribution(config, exchange, starting_balance) - - _create_wallet_history_entries(config, balance_dist, pairlist_valid, config["stake_currency"]) + if not balance_dist.empty and pairlist_valid: + _create_wallet_history_entries( + config, balance_dist, pairlist_valid, config["stake_currency"] + ) -def _prepare_balance_distribution(config: Config, exchange: Exchange, starting_balance: float): +def _prepare_balance_distribution( + config: Config, exchange: Exchange, starting_balance: float +) -> tuple[pd.DataFrame, list[str]]: trade_df = trade_list_to_dataframe(Trade.get_trades_proxy(), minified=False) if trade_df.empty: # no trades, nothing to do - return + return pd.DataFrame(), [] pairlist = list(trade_df["pair"].unique()) timeframe = "1d" stake_currency = config["stake_currency"] @@ -78,7 +82,7 @@ def _prepare_balance_distribution(config: Config, exchange: Exchange, starting_b logger.warning( "No OHLCV data available for the trading pairs; skipping wallet history migration." ) - return + return pd.DataFrame(), [] merged = pd.concat(dfs, axis=1) balance_dist = balance_dist.join(merged, how="left") From 138b70a2bf9aa69bf3dbae377adb78b833f011e7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 27 Jan 2026 19:56:37 +0100 Subject: [PATCH 118/315] test: add explicit test for prepare_balance_distribution --- tests/util/test_historic_wallets_migration.py | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/tests/util/test_historic_wallets_migration.py b/tests/util/test_historic_wallets_migration.py index 2de03a7a3..2048d588b 100644 --- a/tests/util/test_historic_wallets_migration.py +++ b/tests/util/test_historic_wallets_migration.py @@ -1,6 +1,7 @@ from datetime import datetime, timedelta from unittest.mock import MagicMock +import pandas as pd import pytest from freqtrade.enums import CandleType @@ -8,6 +9,7 @@ from freqtrade.persistence import KeyValueStore, Order, Trade, WalletHistory from freqtrade.util import dt_now, dt_utc from freqtrade.util.migrations.migrate_wallet_history import ( _migrate_wallet_history, + _prepare_balance_distribution, migrate_wallet_history, ) from tests.conftest import EXMS, generate_test_data, get_patched_exchange, log_has_re @@ -420,3 +422,77 @@ def test_migrate_wallet_history_db_error_handling( # Migration flag should still be set even after error in _migrate assert KeyValueStore.get_int_value("wallet_history_migration") == 1 + + +@pytest.mark.usefixtures("init_persistence") +def test__prepare_balance_distribution(default_conf_usdt, fee, time_machine, markets): + """Test migration with multiple trading pairs.""" + start_time = dt_utc(2024, 1, 15, 12, 0, 0) + time_machine.move_to(start_time, tick=False) + + # Bot started 15 days ago + bot_start = start_time - timedelta(days=15) + KeyValueStore.store_value("bot_start_time", bot_start) + + # Create mock trades for multiple pairs within the date range + trade1 = create_mock_trade_for_wallet( + fee, + "ETH/USDT", + open_date=start_time - timedelta(days=10), + close_date=start_time - timedelta(days=6), + ) + trade2 = create_mock_trade_for_wallet( + fee, + "BTC/USDT", + open_date=start_time - timedelta(days=7), + close_date=start_time - timedelta(days=5), + ) + Trade.session.add(trade1) + Trade.session.add(trade2) + Trade.commit() + + # Generate mock OHLCV data for both pairs starting from bot_start + candle_type = default_conf_usdt.get("candle_type_def", CandleType.SPOT) + ohlcv_data = {} + ohlcv_data[("ETH/USDT", "1d", candle_type)] = generate_test_data( + "1d", size=20, start=bot_start.strftime("%Y-%m-%d"), base=1500 + ) + + ohlcv_data[("BTC/USDT", "1d", candle_type)] = generate_test_data( + "1d", size=20, start=bot_start.strftime("%Y-%m-%d"), base=30000 + ) + + exchange = MagicMock() + exchange.get_option.return_value = True + exchange.markets = markets + exchange.refresh_latest_ohlcv.return_value = ohlcv_data + + balance_dist, pairlist_valid = _prepare_balance_distribution( + default_conf_usdt, exchange, 1000.0 + ) + assert not balance_dist.empty + assert len(pairlist_valid) == 2 + assert "ETH/USDT" in pairlist_valid + assert "BTC/USDT" in pairlist_valid + + assert len(balance_dist) == 16 # 16 days from bot_start to now + assert balance_dist["USDT"].iloc[0] == 1000.0 + assert pd.isna(balance_dist["USDT"]).sum() == 0 + + assert all( + col in balance_dist.columns + for col in [ + "USDT", + "ETH/USDT", + "ETH/USDT_collateral", + "ETH/USDT_leverage", + "BTC/USDT", + "BTC/USDT_collateral", + "BTC/USDT_leverage", + "ETH/USDT_open", + "BTC/USDT_open", + "ETH/USDT_value", + "BTC/USDT_value", + "total_value", + ] + ) From f9db85fcafc33e35424639a4a851ee7bff8e767f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 Jan 2026 07:11:28 +0100 Subject: [PATCH 119/315] chore: slightly reorder parallelism code --- freqtrade/data/btanalysis/trade_parallelism.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index 9e023a6b5..22a63e14d 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -116,9 +116,9 @@ def balance_distribution_over_time( pair = trade.pair end_date = trade.close_date if trade.close_date is not pd.NaT else None # Exclude open orders - these won't have order_filled_timestamp set. - orders = [o for o in trade.orders if o["order_filled_timestamp"]] df.loc[trade.open_date : end_date, f"{pair}_leverage"] = trade.leverage df.loc[trade.open_date : end_date, f"{pair}_is_short"] = 1 if trade.is_short else 0 + orders = [o for o in trade.orders if o["order_filled_timestamp"]] for order in sorted(orders, key=lambda x: x["order_filled_timestamp"]): filled_at = pd.Timestamp(dt_from_ts(order["order_filled_timestamp"])) real_amount = order.get("filled", order["amount"]) From 5e6d3e265b216d53cb284efc09b575330204bc43 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 29 Jan 2026 07:11:52 +0100 Subject: [PATCH 120/315] tests: rename mock helper function --- tests/util/test_historic_wallets_migration.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/util/test_historic_wallets_migration.py b/tests/util/test_historic_wallets_migration.py index 2048d588b..6eecae762 100644 --- a/tests/util/test_historic_wallets_migration.py +++ b/tests/util/test_historic_wallets_migration.py @@ -15,7 +15,7 @@ from freqtrade.util.migrations.migrate_wallet_history import ( from tests.conftest import EXMS, generate_test_data, get_patched_exchange, log_has_re -def create_mock_trade_for_wallet(fee, pair: str, open_date: datetime, close_date: datetime): +def create_closed_mock_trade(fee, pair: str, open_date: datetime, close_date: datetime): """Create a closed trade for wallet history testing.""" trade = Trade( pair=pair, @@ -146,7 +146,7 @@ def test_migrate_wallet_history_with_trades(default_conf_usdt, fee, time_machine # Create mock trades with dates within the range trade_open = start_time - timedelta(days=5) trade_close = start_time - timedelta(days=3) - trade1 = create_mock_trade_for_wallet( + trade1 = create_closed_mock_trade( fee, "ETH/USDT", open_date=trade_open, @@ -186,13 +186,13 @@ def test_migrate_wallet_history_with_multiple_pairs(default_conf_usdt, fee, time KeyValueStore.store_value("bot_start_time", bot_start) # Create mock trades for multiple pairs within the date range - trade1 = create_mock_trade_for_wallet( + trade1 = create_closed_mock_trade( fee, "ETH/USDT", open_date=start_time - timedelta(days=10), close_date=start_time - timedelta(days=6), ) - trade2 = create_mock_trade_for_wallet( + trade2 = create_closed_mock_trade( fee, "BTC/USDT", open_date=start_time - timedelta(days=7), @@ -258,7 +258,7 @@ def test_migrate_wallet_history_pair_not_in_markets( KeyValueStore.store_value("bot_start_time", bot_start) # Create a trade with a pair that won't be in markets - trade1 = create_mock_trade_for_wallet( + trade1 = create_closed_mock_trade( fee, "UNKNOWN/USDT", open_date=start_time - timedelta(days=5), @@ -289,7 +289,7 @@ def test_migrate_wallet_history_stores_migration_date( KeyValueStore.store_value("bot_start_time", bot_start) # Create a trade - trade1 = create_mock_trade_for_wallet( + trade1 = create_closed_mock_trade( fee, "ETH/USDT", open_date=start_time - timedelta(days=5), @@ -349,7 +349,7 @@ def test_migrate_wallet_history_with_patched_exchange(mocker, default_conf_usdt, KeyValueStore.store_value("bot_start_time", bot_start) # Create a trade - trade1 = create_mock_trade_for_wallet( + trade1 = create_closed_mock_trade( fee, "ETH/USDT", open_date=start_time - timedelta(days=5), @@ -391,7 +391,7 @@ def test_migrate_wallet_history_db_error_handling( KeyValueStore.store_value("bot_start_time", bot_start) # Create a trade - trade1 = create_mock_trade_for_wallet( + trade1 = create_closed_mock_trade( fee, "ETH/USDT", open_date=start_time - timedelta(days=5), @@ -435,13 +435,13 @@ def test__prepare_balance_distribution(default_conf_usdt, fee, time_machine, mar KeyValueStore.store_value("bot_start_time", bot_start) # Create mock trades for multiple pairs within the date range - trade1 = create_mock_trade_for_wallet( + trade1 = create_closed_mock_trade( fee, "ETH/USDT", open_date=start_time - timedelta(days=10), close_date=start_time - timedelta(days=6), ) - trade2 = create_mock_trade_for_wallet( + trade2 = create_closed_mock_trade( fee, "BTC/USDT", open_date=start_time - timedelta(days=7), From a9bbc45ba5a76432213d260d42191a8f24a68cc6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Feb 2026 07:16:41 +0100 Subject: [PATCH 121/315] fix(migration): stake should be non-leveraged. --- freqtrade/data/btanalysis/trade_parallelism.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index 22a63e14d..d03c8eb48 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -123,16 +123,17 @@ def balance_distribution_over_time( filled_at = pd.Timestamp(dt_from_ts(order["order_filled_timestamp"])) real_amount = order.get("filled", order["amount"]) stake = order["safe_price"] * real_amount + stake_no_lev = stake / trade.leverage if order["ft_is_entry"]: fee = stake * trade.fee_open df.loc[filled_at:end_date, pair] += real_amount - df.loc[filled_at:end_date, f"{pair}_collateral"] += stake / trade.leverage - df.loc[filled_at:, stake_currency] -= stake + fee + df.loc[filled_at:end_date, f"{pair}_collateral"] += stake_no_lev + df.loc[filled_at:, stake_currency] -= stake_no_lev + fee else: fee = stake * trade.fee_close df.loc[filled_at:end_date, pair] -= real_amount - df.loc[filled_at:end_date, f"{pair}_collateral"] -= stake / trade.leverage - df.loc[filled_at:, stake_currency] += stake - fee + df.loc[filled_at:end_date, f"{pair}_collateral"] -= stake_no_lev + df.loc[filled_at:, stake_currency] += stake_no_lev - fee # Round to avoid floating point issues df = df.round(14) From 35806c26bf8fa9b48f1cc3df38ed6f69e76d2f2f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Feb 2026 20:33:16 +0100 Subject: [PATCH 122/315] fix: Improved migration for short trades --- .../data/btanalysis/trade_parallelism.py | 21 ++++++++++++++++++- .../util/migrations/migrate_wallet_history.py | 13 +++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index d03c8eb48..70b418b49 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -119,21 +119,40 @@ def balance_distribution_over_time( df.loc[trade.open_date : end_date, f"{pair}_leverage"] = trade.leverage df.loc[trade.open_date : end_date, f"{pair}_is_short"] = 1 if trade.is_short else 0 orders = [o for o in trade.orders if o["order_filled_timestamp"]] + current_position = 0 + current_collateral = 0 for order in sorted(orders, key=lambda x: x["order_filled_timestamp"]): filled_at = pd.Timestamp(dt_from_ts(order["order_filled_timestamp"])) real_amount = order.get("filled", order["amount"]) stake = order["safe_price"] * real_amount stake_no_lev = stake / trade.leverage if order["ft_is_entry"]: + # Entry order: lock collateral and pay fee + # For both long and short: balance decreases by collateral + fee fee = stake * trade.fee_open + current_position += real_amount + current_collateral += stake_no_lev df.loc[filled_at:end_date, pair] += real_amount df.loc[filled_at:end_date, f"{pair}_collateral"] += stake_no_lev df.loc[filled_at:, stake_currency] -= stake_no_lev + fee else: + # Exit order: release collateral and realize profit/loss fee = stake * trade.fee_close + if trade.is_short: + # For SHORT + df.loc[filled_at:, stake_currency] += ( + current_collateral * (1 + trade.leverage) - stake + ) + current_collateral * (1 + trade.leverage) - stake + else: + # For LONG + df.loc[filled_at:, stake_currency] += stake - current_collateral * ( + trade.leverage - 1 + ) df.loc[filled_at:end_date, pair] -= real_amount df.loc[filled_at:end_date, f"{pair}_collateral"] -= stake_no_lev - df.loc[filled_at:, stake_currency] += stake_no_lev - fee + current_position -= real_amount + current_collateral -= stake_no_lev # Round to avoid floating point issues df = df.round(14) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 7c91a9975..eb24c5922 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -1,5 +1,6 @@ import logging +import numpy as np import pandas as pd from freqtrade.constants import Config @@ -90,7 +91,17 @@ def _prepare_balance_distribution( index=balance_dist.index, columns=[f"{p}_value" for p in pairlist_valid], dtype=float ) for p in pairlist_valid: - df_value[f"{p}_value"] = balance_dist[f"{p}_open"] * balance_dist[p] + # df_value[f"{p}_value"] = balance_dist[f"{p}_open"] * balance_dist[p] + # Identical calculation to rpc and wallets.py + df_value[f"{p}_value"] = np.where( + balance_dist[f"{p}_is_short"] == 0, + (balance_dist[f"{p}_open"] * balance_dist[p]) + - balance_dist[f"{p}_collateral"] * (balance_dist[f"{p}_leverage"] - 1), + ( + balance_dist[f"{p}_collateral"] * (1 + balance_dist[f"{p}_leverage"]) + - balance_dist[f"{p}_open"] * balance_dist[p] + ), + ) balance_dist = pd.concat([balance_dist, df_value], axis=1) # Aggregate total value at each point in time From c3b2a73eefb34375745cc1b295093c6ad6140ffa Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 3 Feb 2026 20:33:28 +0100 Subject: [PATCH 123/315] feat: warn for pairs without history --- freqtrade/util/migrations/migrate_wallet_history.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index eb24c5922..30a31b8e7 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -59,6 +59,12 @@ def _prepare_balance_distribution( pairlist=pairlist, ) pairlist_valid = [p for p in pairlist if p in exchange.markets] + pairlist_invalid = set(pairlist) - set(pairlist_valid) + if pairlist_invalid: + logger.warning( + f"The following trading pairs from the trade history are not available on the exchange " + f"and will be skipped during wallet history migration: {', '.join(pairlist_invalid)}" + ) logger.info("Wallet History migration: Fetching OHLCV data ...") data = exchange.refresh_latest_ohlcv( From 425ebeeedbc9c9f9370b688a7891dbb6e5ea1eea Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Mar 2026 10:40:35 +0100 Subject: [PATCH 124/315] fix: include fees on both trade sides --- freqtrade/data/btanalysis/trade_parallelism.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/freqtrade/data/btanalysis/trade_parallelism.py b/freqtrade/data/btanalysis/trade_parallelism.py index 70b418b49..8ed9702e0 100644 --- a/freqtrade/data/btanalysis/trade_parallelism.py +++ b/freqtrade/data/btanalysis/trade_parallelism.py @@ -129,25 +129,24 @@ def balance_distribution_over_time( if order["ft_is_entry"]: # Entry order: lock collateral and pay fee # For both long and short: balance decreases by collateral + fee - fee = stake * trade.fee_open + fee_open = stake * trade.fee_open current_position += real_amount current_collateral += stake_no_lev df.loc[filled_at:end_date, pair] += real_amount df.loc[filled_at:end_date, f"{pair}_collateral"] += stake_no_lev - df.loc[filled_at:, stake_currency] -= stake_no_lev + fee + df.loc[filled_at:, stake_currency] -= stake_no_lev + fee_open else: # Exit order: release collateral and realize profit/loss - fee = stake * trade.fee_close + fee_close = stake * trade.fee_close if trade.is_short: # For SHORT df.loc[filled_at:, stake_currency] += ( current_collateral * (1 + trade.leverage) - stake - ) - current_collateral * (1 + trade.leverage) - stake + ) - fee_close else: # For LONG - df.loc[filled_at:, stake_currency] += stake - current_collateral * ( - trade.leverage - 1 + df.loc[filled_at:, stake_currency] += ( + stake - current_collateral * (trade.leverage - 1) - fee_close ) df.loc[filled_at:end_date, pair] -= real_amount df.loc[filled_at:end_date, f"{pair}_collateral"] -= stake_no_lev From e28da4b0874a6cce8085dabcf07ef3a94b391a61 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 22 Mar 2026 19:20:02 +0100 Subject: [PATCH 125/315] test: Fix and improve balance distribution test --- tests/data/test_trade_parallelism.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/data/test_trade_parallelism.py b/tests/data/test_trade_parallelism.py index 1aadf99f7..266c59446 100644 --- a/tests/data/test_trade_parallelism.py +++ b/tests/data/test_trade_parallelism.py @@ -172,6 +172,9 @@ def test_balance_distribution_over_time(is_short): assert stake_currency in result.columns for pair in pairlist: assert pair in result.columns + assert f"{pair}_leverage" in result.columns + assert f"{pair}_is_short" in result.columns + assert f"{pair}_collateral" in result.columns # Verify the index is a DatetimeIndex assert isinstance(result.index, Timestamp.__class__.__bases__[0]) @@ -195,14 +198,13 @@ def test_balance_distribution_over_time(is_short): assert all(btc_after_close == 0), "Position should be 0 after trade closes" # Final stake currency should reflect all trades' cash flows minus fees - # The function tracks cash flow: entries subtract stake, exits add stake - # Both long and short use the same formula based on order prices final_balance = result.iloc[-1][stake_currency] # Verify the balance changed (trades had effect) assert final_balance != start_balance, "Balance should change after trading" # Since all exit prices > entry prices, exits return more cash than entries spent - # This means final balance > start balance for both long and short trades - # (the function tracks cash flow, not P&L from long/short perspective) - assert final_balance > start_balance, "Exit prices > entry prices should increase balance" + # This means final balance > start balance for long trades and < start balance for short trades + assert (final_balance > start_balance) if not is_short else (final_balance < start_balance), ( + "Balance increases for long and decreases for short trades" + ) From 78ccb68929e4a7e869f776bb3551145099845497 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 11:50:47 +0200 Subject: [PATCH 126/315] fix: adjust backtest-logic to new capture method --- freqtrade/rpc/api_server/api_backtest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/api_server/api_backtest.py b/freqtrade/rpc/api_server/api_backtest.py index 772b33f99..afbd44101 100644 --- a/freqtrade/rpc/api_server/api_backtest.py +++ b/freqtrade/rpc/api_server/api_backtest.py @@ -380,8 +380,8 @@ def api_get_backtest_wallet(file: str, strategy: str, config=Depends(get_config) if results is None: raise HTTPException(status_code=404, detail="Unable to retrieve wallet history.") # Consolidate the wallet to the base currency - results.loc[:, "total"] = results["price"] * results["balance"] - results = results.groupby(["date", "__date_ts"]).agg({"total": "sum"}).reset_index() + results.loc[:, "total_quote"] = results["rate"] * results["balance"] + results = results.groupby(["date", "__date_ts"]).agg({"total_quote": "sum"}).reset_index() return { "columns": results.columns.tolist(), From f99ccc5e3d05d0da40d32a62880ab15ed5424c7d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 11:52:46 +0200 Subject: [PATCH 127/315] docs: improved backtesting doc wording --- docs/backtesting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 5f42a6bcd..12455b367 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -417,7 +417,7 @@ It contains key metrics about the performance of your strategy on backtesting da - `Entry/Exit Timeouts`: Entry/exit orders which did not fill (only applicable if custom pricing is used). - `Min/Max balance realized`: Lowest and Highest Wallet balance during the backtest period based on closed trades trades. - `Min/Max balance unrealized`: Lowest and Highest Wallet balance during the backtest period - including capital tied in open trades. -- `Min/Max balance dates`: Dates when the minimum and maximum balance occurred. +- `Min/Max balance dates`: Dates when the minimum and maximum unrealized balance occurred. - `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`: Maximum absolute drawdown experienced, including percentage relative to the account calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.. - `Drawdown duration`: Duration of the largest drawdown period. From dec3c3e13b9daf850b0e495657d435e21f3ce17f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 12:12:11 +0200 Subject: [PATCH 128/315] test: fix a couple tsts ... --- tests/rpc/test_rpc_apiserver.py | 4 ++-- tests/test_wallets.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index af5f5e5bf..95cb9512a 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -3332,7 +3332,7 @@ def test_api_backtest_wallets(botclient, tmp_path: Path): "2018-01-01T00:05:00Z", ], "currency": ["ETH", "BTC", "ETH", "BTC"], - "price": [2000, 60_000, 2001, 60_001], + "rate": [2000, 60_000, 2001, 60_001], "balance": [0.5, 0.25, 0.5, 0.25], } ) @@ -3357,7 +3357,7 @@ def test_api_backtest_wallets(botclient, tmp_path: Path): assert_response(rc, 200) result = rc.json() assert result["length"] == 2 - assert result["columns"] == ["date", "__date_ts", "total"] + assert result["columns"] == ["date", "__date_ts", "total_quote"] assert result["data"] == [ ["2018-01-01T00:00:00Z", 1514764800000, 16000.0], ["2018-01-01T00:05:00Z", 1514765100000, 16000.75], diff --git a/tests/test_wallets.py b/tests/test_wallets.py index b14a5e602..2f37bf5a2 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -633,7 +633,7 @@ def test_record_wallet_state_stores_wallet_history(mocker, default_conf_usdt): mocker.patch.object( freqtrade.exchange, "get_conversion_rate", - side_effect=lambda currency, _: conversion_rates.get(currency, 1.0), + side_effect=lambda currency, *args, **kwargs: conversion_rates.get(currency, 1.0), ) mocker.patch( "freqtrade.persistence.trade_model.Trade.get_open_trades", From d001d9164008e696d00d004d2334a34a609a2752 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 12:50:10 +0200 Subject: [PATCH 129/315] chore: rename temporary column for clarity --- .../optimize_reports/optimize_reports.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index ba0b4304a..a8eefbc7b 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -47,15 +47,15 @@ def generate_wallet_stats(wallet_df: DataFrame, stake_currency: str) -> dict[str """Generate wallet statistics from the wallet DataFrame.""" if wallet_df is None or wallet_df.empty: return {} - wallet_df.loc[:, "total"] = wallet_df["rate"] * wallet_df["balance"] + wallet_df.loc[:, "total_quote"] = wallet_df["rate"] * wallet_df["balance"] # Group by date to get total wallet value at each timestamp - wallet = wallet_df.groupby("date")["total"].sum().reset_index() - start_balance = wallet.iloc[0]["total"] - end_balance = wallet.iloc[-1]["total"] - high_balance = wallet["total"].max() - low_balance = wallet["total"].min() - low_date = wallet.iloc[wallet["total"].idxmin()]["date"] - high_date = wallet.iloc[wallet["total"].idxmax()]["date"] + wallet = wallet_df.groupby("date")["total_quote"].sum().reset_index() + start_balance = wallet.iloc[0]["total_quote"] + end_balance = wallet.iloc[-1]["total_quote"] + high_balance = wallet["total_quote"].max() + low_balance = wallet["total_quote"].min() + low_date = wallet.iloc[wallet["total_quote"].idxmin()]["date"] + high_date = wallet.iloc[wallet["total_quote"].idxmax()]["date"] return { "start_balance": start_balance, "end_balance": end_balance, From 66eb7f019901552ee3b35e0d690f5d6a8011003f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 13:05:57 +0200 Subject: [PATCH 130/315] test: add test for historic_balance endpoint --- tests/rpc/test_rpc_apiserver.py | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 95cb9512a..63a1d6efd 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1436,6 +1436,41 @@ def test_api_stats(botclient, mocker, ticker, fee, markets, is_short): assert "draws" in rc.json()["durations"] +@pytest.mark.parametrize("is_short", [True, False]) +def test_api_historic_balance(botclient, mocker, ticker, fee, markets, is_short): + ftbot, client = botclient + patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) + mocker.patch.multiple( + EXMS, + get_balances=MagicMock(return_value=ticker), + fetch_ticker=ticker, + get_fee=fee, + markets=PropertyMock(return_value=markets), + ) + + rc = client_get(client, f"{BASE_URI}/historic_balance") + assert_response(rc, 200) + resp = rc.json() + assert "columns" in resp + assert "data" in resp + assert "length" in resp + assert "capture_start_ts" in resp + assert resp["length"] == 0 + + ftbot.wallets.record_wallet_state() + + rc = client_get(client, f"{BASE_URI}/historic_balance") + assert_response(rc, 200) + resp1 = rc.json() + assert "columns" in resp1 + assert "data" in resp1 + assert "length" in resp1 + assert "capture_start_ts" in resp1 + assert resp1["length"] == 1 + assert "__date_ts" in resp1["columns"] + assert "total_quote" in resp1["columns"] + + def test_api_performance(botclient, fee): ftbot, client = botclient patch_get_signal(ftbot) From a40de0a59be2b8c8aa855eb1a609a03852231234 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 13:10:35 +0200 Subject: [PATCH 131/315] chore: rename variable for better debuggability --- freqtrade/rpc/rpc.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index ecb17128a..0c5f46f01 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -795,10 +795,12 @@ class RPC: results = results.rename({"timestamp": "date"}, axis=1) results.loc[:, "__date_ts"] = results.loc[:, "date"].astype("int64") // 1000 // 1000 # Exclude non-bot managed for now - results = results.loc[results["bot_managed"]] + results_filtered = results.loc[results["bot_managed"]] results_final = ( - results.groupby(["date", "__date_ts"]).agg({"total_quote": "sum"}).reset_index() + results_filtered.groupby(["date", "__date_ts"]) + .agg({"total_quote": "sum"}) + .reset_index() ) hist = KeyValueStore.get_datetime_value("wallet_history_migration_date") return results_final, dt_ts_def(hist, 0) From 75bfa87f747d1d85ba4247df888838dcb617ea07 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 13:12:03 +0200 Subject: [PATCH 132/315] chore: minor code cleanup --- freqtrade/wallets.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 41ce229c6..e4fcdd269 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -447,9 +447,7 @@ class Wallets: logger.info(msg) def record_wallet_state(self) -> None: - """ - Record daily wallet totals to database - """ + """Record daily wallet totals to database""" if self._is_backtest: # only record in live mode. return @@ -487,7 +485,7 @@ class Wallets: wallet_records.append(position_record) for wallet in self.get_all_balances().values(): - # TODO: exclude minimal balances? + # TODO: (needs decision) exclude minimal balances? rate = self._exchange.get_conversion_rate(wallet.currency, self._stake_currency) is_bot_managed = ( self._stake_currency == wallet.currency or wallet.currency in open_assets From 7af322a197fd2d41668d7e3fcefd0bfa3ad39020 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 13:17:48 +0200 Subject: [PATCH 133/315] fix: impove behavior when loading old backtest results --- freqtrade/data/btanalysis/bt_fileutils.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/freqtrade/data/btanalysis/bt_fileutils.py b/freqtrade/data/btanalysis/bt_fileutils.py index 103abae5b..a97d5bef3 100644 --- a/freqtrade/data/btanalysis/bt_fileutils.py +++ b/freqtrade/data/btanalysis/bt_fileutils.py @@ -312,18 +312,25 @@ def get_backtest_market_change(filename: Path, include_ts: bool = True) -> pd.Da return df -def get_backtest_wallet_change(filename: Path, strategy_name: str) -> pd.DataFrame: +def get_backtest_wallet_change(filename: Path, strategy_name: str) -> pd.DataFrame | None: """ Read backtest wallet change file. :param filename: Path to the backtest result zip file :param strategy_name: Name of the strategy to load :return: DataFrame with wallet change data """ - data = load_file_from_zip(filename, f"{filename.stem}_{strategy_name}_wallet.feather") - df = pd.read_feather(BytesIO(data)) + if filename.suffix != ".zip": + return None - df.loc[:, "__date_ts"] = df.loc[:, "date"].astype(np.int64) // 1000 // 1000 - return df + try: + data = load_file_from_zip(filename, f"{filename.stem}_{strategy_name}_wallet.feather") + df = pd.read_feather(BytesIO(data)) + + df.loc[:, "__date_ts"] = df.loc[:, "date"].astype(np.int64) // 1000 // 1000 + return df + except ValueError: + pass + return None def find_existing_backtest_stats( From b45e086b50d315ea7e73424addf5cb8a56381077 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 13:38:35 +0200 Subject: [PATCH 134/315] test: add tests for get_backtest_wallet_change (and market change) --- tests/data/test_btanalysis.py | 55 +++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 318918044..82ff56c3a 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -11,6 +11,8 @@ from freqtrade.constants import LAST_BT_RESULT_FN from freqtrade.data.btanalysis import ( BT_DATA_COLUMNS, extract_trades_of_period, + get_backtest_market_change, + get_backtest_wallet_change, get_latest_backtest_filename, get_latest_hyperopt_file, load_backtest_data, @@ -637,3 +639,56 @@ def test_load_file_from_zip(tmp_path): with pytest.raises(ValueError, match=r"File .* not found in zip.*"): load_file_from_zip(zip_file, "testfile55.txt") + + +def test_get_backtest_market_change(tmp_path): + df = DataFrame( + { + "date": [dt_utc(2020, 1, 1), dt_utc(2020, 1, 2)], + "price": [100.0, 110.0], + } + ) + feather_file = tmp_path / "backtest-result_market_change.feather" + df.to_feather(feather_file) + + direct_df = get_backtest_market_change(feather_file) + assert isinstance(direct_df, DataFrame) + assert "__date_ts" in direct_df.columns + assert direct_df.loc[0, "__date_ts"] == int(df.loc[0, "date"].timestamp() * 1000) + + no_ts_df = get_backtest_market_change(feather_file, include_ts=False) + assert "__date_ts" not in no_ts_df.columns + + zip_file = tmp_path / "backtest-result.zip" + with ZipFile(zip_file, "w") as zipf: + zipf.write(feather_file, arcname=f"{zip_file.stem}_market_change.feather") + + zipped_df = get_backtest_market_change(zip_file) + assert isinstance(zipped_df, DataFrame) + assert zipped_df.loc[0, "__date_ts"] == int(df.loc[0, "date"].timestamp() * 1000) + assert list(zipped_df["price"]) == [100.0, 110.0] + + +def test_get_backtest_wallet_change(tmp_path): + df = DataFrame( + { + "date": [dt_utc(2020, 1, 1), dt_utc(2020, 1, 2)], + "balance": [1.0, 1.1], + "rate": [1.0, 1.1], + } + ) + wallet_feather = tmp_path / "backtest-result_TestStrategy_wallet.feather" + df.to_feather(wallet_feather) + + zip_file = tmp_path / "backtest-result.zip" + with ZipFile(zip_file, "w") as zipf: + zipf.write(wallet_feather, arcname=wallet_feather.name) + + wallet_df = get_backtest_wallet_change(zip_file, "TestStrategy") + assert isinstance(wallet_df, DataFrame) + assert "__date_ts" in wallet_df.columns + assert wallet_df.loc[0, "__date_ts"] == int(df.loc[0, "date"].timestamp() * 1000) + assert list(wallet_df["balance"]) == [1.0, 1.1] + + assert get_backtest_wallet_change(tmp_path / "backtest-result.feather", "TestStrategy") is None + assert get_backtest_wallet_change(zip_file, "UnknownStrategy") is None From 754f24c8a684ade59dfccdcfa1e2e1a0aca67fd5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 13:48:54 +0200 Subject: [PATCH 135/315] feat: allow skipping of the wallet migration fallback method in case of problems --- build_helpers/schema.json | 4 ++++ freqtrade/config_schema/config_schema.py | 4 ++++ freqtrade/util/migrations/migrate_wallet_history.py | 4 +++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/build_helpers/schema.json b/build_helpers/schema.json index 8bf56b2a1..5d07b4ad1 100644 --- a/build_helpers/schema.json +++ b/build_helpers/schema.json @@ -283,6 +283,10 @@ "month" ] }, + "skip_wallet_history_migration": { + "description": "Disable wallet history migration.", + "type": "boolean" + }, "hyperopt_path": { "description": "Specify additional lookup path for Hyperopt Loss functions.", "type": "string" diff --git a/freqtrade/config_schema/config_schema.py b/freqtrade/config_schema/config_schema.py index fc2c42441..7fc885662 100644 --- a/freqtrade/config_schema/config_schema.py +++ b/freqtrade/config_schema/config_schema.py @@ -236,6 +236,10 @@ CONF_SCHEMA = { "type": "string", "enum": BACKTEST_CACHE_AGE, }, + "skip_wallet_history_migration": { + "description": "Disable wallet history migration.", + "type": "boolean", + }, # Hyperopt "hyperopt_path": { "description": "Specify additional lookup path for Hyperopt Loss functions.", diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 30a31b8e7..82af820d3 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -16,7 +16,9 @@ logger = logging.getLogger(__name__) def migrate_wallet_history(config: Config, exchange: Exchange, starting_balance: float): - if not exchange.get_option("ohlcv_has_history", True): + if config.get("skip_wallet_history_migration") or not exchange.get_option( + "ohlcv_has_history", True + ): # we can't fill up wallet history without ohlcv history return if KeyValueStore.get_int_value("wallet_history_migration"): From af815a3c766ca4da2f51224d5a12a0e0c5ec5c42 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 13:51:11 +0200 Subject: [PATCH 136/315] chore: unify treatment of pos.leverage fallbacks --- freqtrade/wallets.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index e4fcdd269..e3ac1288b 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -461,12 +461,13 @@ class Wallets: base = self._exchange.get_pair_base_currency(pos.symbol) rate = self._exchange.get_conversion_rate(base, self._stake_currency) total_quote = None + leverage = pos.leverage or 1.0 if rate: # Same formula than in rpc's _rpc_balance total_quote = ( - rate * pos.position - pos.collateral * (pos.leverage - 1) + rate * pos.position - pos.collateral * (leverage - 1) if pos.side == "long" - else pos.collateral * (1 + pos.leverage) - rate * pos.position + else pos.collateral * (1 + leverage) - rate * pos.position ) position_record = WalletHistory( @@ -478,7 +479,7 @@ class Wallets: total_quote=total_quote, total_position_value=rate * pos.position if rate else None, collateral=pos.collateral, - leverage=pos.leverage or 1.0, + leverage=leverage, bot_managed=base in open_assets, ) position_collaterals += pos.collateral From b6b9ae5eb0e48dcc2e03c9a3c84ba6394c74c786 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 14:09:36 +0200 Subject: [PATCH 137/315] test: assert repr for walletHistory --- tests/test_wallets.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_wallets.py b/tests/test_wallets.py index 2f37bf5a2..20861108a 100644 --- a/tests/test_wallets.py +++ b/tests/test_wallets.py @@ -646,6 +646,8 @@ def test_record_wallet_state_stores_wallet_history(mocker, default_conf_usdt): wallet_entries = WalletHistory.session.query(WalletHistory).all() assert len(wallet_entries) == 3 + assert "total_quote" in repr(wallet_entries[0]) + assert "WalletHistory(" in repr(wallet_entries[0]) records_by_currency = {entry.currency: entry for entry in wallet_entries} assert records_by_currency[stake_currency].balance == 149 From 51478baa475e8b2a84ba51c46aaec2b38875fbe5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Apr 2026 19:46:46 +0200 Subject: [PATCH 138/315] docs: Add Dashboard section to freqUI docs --- docs/freq-ui.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/freq-ui.md b/docs/freq-ui.md index ff9758888..efdea7ff0 100644 --- a/docs/freq-ui.md +++ b/docs/freq-ui.md @@ -46,6 +46,23 @@ On this page, you can also interact with the bot by starting and stopping it and ![FreqUI - trade view](assets/freqUI-trade-pane-dark.png#only-dark) ![FreqUI - trade view](assets/freqUI-trade-pane-light.png#only-light) +### Dashboard + +The dashboard view provides an overview of the bot's performance and status. +If multiple bots are connected, the dashboard will show an overview of all connected bots, allowing you to easily switch between them or show just a subset of available bots. + +#### Wallet Balance + +New in freqtrade 2026.4 shows the balance of the bot over time. + +Compared to the "cumulative Profit" chart, this chart will show the actual balance of the bot over time, including unrealized profit and losses, as well as deposits and withdrawals. + +Historic data has re-populated based on available exchange data - however is assumed to be best-effort and may not be 100% accurate. +More specifically, it won't cover deposits and withdrawals, and will assume a starting balance of current balance - profit/losses. + +For clarity - a "Capture start" marker line is shown on the chart, which indicates the point at which the migration to the new wallet balance tracking system happened. +Only beyond this point, the wallet balance is expected to be accurate. + ### Plot Configurator FreqUI Plots can be configured either via a `plot_config` configuration object in the strategy (which can be loaded via "from strategy" button) or via the UI. From 8768fe90b33c3c09ae42637dc78154e018e3f442 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 5 Apr 2026 10:08:50 +0200 Subject: [PATCH 139/315] refactor: slightly improve generate_wallet_stats --- .../optimize/optimize_reports/optimize_reports.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index a8eefbc7b..bea047202 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -50,12 +50,15 @@ def generate_wallet_stats(wallet_df: DataFrame, stake_currency: str) -> dict[str wallet_df.loc[:, "total_quote"] = wallet_df["rate"] * wallet_df["balance"] # Group by date to get total wallet value at each timestamp wallet = wallet_df.groupby("date")["total_quote"].sum().reset_index() + total_quote = wallet["total_quote"] + low_idx = total_quote.idxmin() + high_idx = total_quote.idxmax() start_balance = wallet.iloc[0]["total_quote"] end_balance = wallet.iloc[-1]["total_quote"] - high_balance = wallet["total_quote"].max() - low_balance = wallet["total_quote"].min() - low_date = wallet.iloc[wallet["total_quote"].idxmin()]["date"] - high_date = wallet.iloc[wallet["total_quote"].idxmax()]["date"] + high_balance = total_quote.loc[high_idx] + low_balance = total_quote.loc[low_idx] + low_date = wallet.loc[low_idx, "date"] + high_date = wallet.loc[high_idx, "date"] return { "start_balance": start_balance, "end_balance": end_balance, From aaadb01a6ae6d15376b923a5d3af79a14421fe7f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 5 Apr 2026 10:08:56 +0200 Subject: [PATCH 140/315] docs: improve doc wording --- docs/freq-ui.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/freq-ui.md b/docs/freq-ui.md index efdea7ff0..d561810f7 100644 --- a/docs/freq-ui.md +++ b/docs/freq-ui.md @@ -53,7 +53,7 @@ If multiple bots are connected, the dashboard will show an overview of all conne #### Wallet Balance -New in freqtrade 2026.4 shows the balance of the bot over time. +New in freqtrade 2026.4: This shows the balance of the bot over time. Compared to the "cumulative Profit" chart, this chart will show the actual balance of the bot over time, including unrealized profit and losses, as well as deposits and withdrawals. From 4ba01b5c12607952eb25ad00cb5ae8417a9d6c5e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 5 Apr 2026 10:10:06 +0200 Subject: [PATCH 141/315] fix: don't assume "/" for pair base currency --- freqtrade/util/migrations/migrate_wallet_history.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index 82af820d3..dbc887c56 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -36,7 +36,7 @@ def _migrate_wallet_history(config: Config, exchange: Exchange, starting_balance balance_dist, pairlist_valid = _prepare_balance_distribution(config, exchange, starting_balance) if not balance_dist.empty and pairlist_valid: _create_wallet_history_entries( - config, balance_dist, pairlist_valid, config["stake_currency"] + config, exchange, balance_dist, pairlist_valid, config["stake_currency"] ) @@ -122,6 +122,7 @@ def _prepare_balance_distribution( def _create_wallet_history_entries( config: Config, + exchange: Exchange, balance_dist: pd.DataFrame, pairlist_valid: list[str], stake_currency: str, @@ -166,7 +167,7 @@ def _create_wallet_history_entries( # Add entries for each trading pair for pair in pairlist_valid: - base_currency = pair.split("/")[0] + base_currency = exchange.get_pair_base_currency(pair) balance = row[pair_balance_idx[pair]] leverage = row[pair_leverage_idx[pair]] # Only add entry if balance is not empty/NaN From de064505c525dd57087cffea41fe2c7658301226 Mon Sep 17 00:00:00 2001 From: Achmad Fathoni Date: Sun, 5 Apr 2026 21:30:37 +0700 Subject: [PATCH 142/315] Enforce SKDecimal 'name' parameter as string --- freqtrade/optimize/space/decimalspace.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/space/decimalspace.py b/freqtrade/optimize/space/decimalspace.py index dc6dba04d..d1036bb93 100644 --- a/freqtrade/optimize/space/decimalspace.py +++ b/freqtrade/optimize/space/decimalspace.py @@ -9,7 +9,7 @@ class SKDecimal(FloatDistribution): *, step: float | None = None, decimals: int | None = None, - name=None, + name: str | None = None, ): """ FloatDistribution with a fixed step size. @@ -26,7 +26,7 @@ class SKDecimal(FloatDistribution): raise ValueError("You must set one of decimals or step") # Convert decimals to step self.step = step or (1 / 10**decimals if decimals else 1) - self.name = name + self.name = name or "" super().__init__( low=round(low, decimals) if decimals else low, From 3d68d6aefc5564162fc0c0b1f909b1886d8fc317 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 5 Apr 2026 17:06:53 +0200 Subject: [PATCH 143/315] test: Add exchange mock for get_pair_base_currency --- tests/util/test_historic_wallets_migration.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/util/test_historic_wallets_migration.py b/tests/util/test_historic_wallets_migration.py index 6eecae762..e4a078201 100644 --- a/tests/util/test_historic_wallets_migration.py +++ b/tests/util/test_historic_wallets_migration.py @@ -164,6 +164,7 @@ def test_migrate_wallet_history_with_trades(default_conf_usdt, fee, time_machine exchange.get_option.return_value = True exchange.markets = markets exchange.refresh_latest_ohlcv.return_value = ohlcv_data + exchange.get_pair_base_currency = MagicMock(side_effect=lambda pair: markets.get(pair)["base"]) migrate_wallet_history(default_conf_usdt, exchange, 1000.0) @@ -217,6 +218,7 @@ def test_migrate_wallet_history_with_multiple_pairs(default_conf_usdt, fee, time exchange.get_option.return_value = True exchange.markets = markets exchange.refresh_latest_ohlcv.return_value = ohlcv_data + exchange.get_pair_base_currency = MagicMock(side_effect=lambda pair: markets.get(pair)["base"]) migrate_wallet_history(default_conf_usdt, exchange, 1000.0) From 69ffc2c7a227d3880d23a542dbf3fc487d588af8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 03:03:44 +0000 Subject: [PATCH 144/315] chore(deps): bump numpy from 2.4.3 to 2.4.4 Bumps [numpy](https://github.com/numpy/numpy) from 2.4.3 to 2.4.4. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst) - [Commits](https://github.com/numpy/numpy/compare/v2.4.3...v2.4.4) --- updated-dependencies: - dependency-name: numpy dependency-version: 2.4.4 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 aa5c3ef7c..1f7405631 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -numpy==2.4.3 +numpy==2.4.4 pandas==2.3.3 bottleneck==1.6.0 numexpr==2.14.1 From 90d444791159336505174231e1cc5b7662fbd2fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 03:03:51 +0000 Subject: [PATCH 145/315] chore(deps): bump fastapi from 0.135.1 to 0.135.2 Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.135.1 to 0.135.2. - [Release notes](https://github.com/fastapi/fastapi/releases) - [Commits](https://github.com/fastapi/fastapi/compare/0.135.1...0.135.2) --- updated-dependencies: - dependency-name: fastapi dependency-version: 0.135.2 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 aa5c3ef7c..8efca9db6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,7 @@ orjson==3.11.7 sdnotify==0.3.2 # API Server -fastapi==0.135.1 +fastapi==0.135.2 pydantic==2.12.5 uvicorn==0.42.0 pyjwt==2.12.1 From bc574509bfd3ac2b60827d66113a1f08ba4f7536 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 03:03:56 +0000 Subject: [PATCH 146/315] chore(deps): bump cmaes from 0.12.0 to 0.13.0 Bumps [cmaes](https://github.com/CyberAgentAILab/cmaes) from 0.12.0 to 0.13.0. - [Release notes](https://github.com/CyberAgentAILab/cmaes/releases) - [Commits](https://github.com/CyberAgentAILab/cmaes/compare/v0.12.0...v0.13.0) --- updated-dependencies: - dependency-name: cmaes dependency-version: 0.13.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index b1f2abd3e..32ef55806 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -6,4 +6,4 @@ scipy==1.17.1 scikit-learn==1.8.0 filelock==3.25.2 optuna==4.8.0 -cmaes==0.12.0 +cmaes==0.13.0 From 13c872d079a213ad376ab969dd0f58d7d4ba8c56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 03:04:08 +0000 Subject: [PATCH 147/315] chore(deps-dev): bump ruff from 0.15.7 to 0.15.8 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.7 to 0.15.8. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.7...0.15.8) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.8 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 19ac6877d..e67e336a8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ -r requirements-freqai-rl.txt -r docs/requirements-docs.txt -ruff==0.15.7 +ruff==0.15.8 mypy==1.19.1 pre-commit==4.5.1 pytest==9.0.2 From 7af050ecc4338006e13f5ba10929ed9513524891 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 03:04:08 +0000 Subject: [PATCH 148/315] chore(deps): bump astral-sh/setup-uv from 7.3.1 to 8.0.0 Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.3.1 to 8.0.0. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/5a095e7a2014a4212f075830d4f7277575a9d098...cec208311dfd045dd5311c1add060b2062131d57) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: 8.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/binance-lev-tier-update.yml | 2 +- .github/workflows/ci.yml | 10 +++++----- .github/workflows/deploy-docs.yml | 2 +- .github/workflows/pre-commit-update.yml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/binance-lev-tier-update.yml b/.github/workflows/binance-lev-tier-update.yml index cce07573c..01040534e 100644 --- a/.github/workflows/binance-lev-tier-update.yml +++ b/.github/workflows/binance-lev-tier-update.yml @@ -29,7 +29,7 @@ jobs: python-version: "3.14" - name: Install uv - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true enable-cache: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c7a5842db..883583851 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: python-version: ${{ matrix.python-version }} - name: Install uv - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true enable-cache: true @@ -183,7 +183,7 @@ jobs: python-version: "3.13" - name: Install uv - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true python-version: "3.13" @@ -225,7 +225,7 @@ jobs: python-version: "3.13" - name: Install uv - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true python-version: "3.13" @@ -262,7 +262,7 @@ jobs: python-version: "${{ matrix.python-version }}" - name: Install uv - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true enable-cache: true @@ -334,7 +334,7 @@ jobs: python-version: "${{ matrix.python-version }}" - name: Install uv - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true python-version: "${{ matrix.python-version }}" diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index edd708274..bd3d0ee9b 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -32,7 +32,7 @@ jobs: python-version: '3.13' - name: Install uv - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true python-version: '3.13' diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml index 723a47d2a..3d74af2c1 100644 --- a/.github/workflows/pre-commit-update.yml +++ b/.github/workflows/pre-commit-update.yml @@ -30,7 +30,7 @@ jobs: python-version: "3.13" - name: Install uv - uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098 # v7.3.1 + uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true python-version: "3.13" From 696af8ec07e6aafe0a88bf1626d0345b678e3201 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 03:04:15 +0000 Subject: [PATCH 149/315] chore(deps): bump pymdown-extensions from 10.21 to 10.21.2 Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.21 to 10.21.2. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.21...10.21.2) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-version: 10.21.2 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 beb93d144..28b850b19 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -2,6 +2,6 @@ markdown==3.10.2 mkdocs==1.6.1 mkdocs-material==9.7.6 mdx_truly_sane_lists==1.3 -pymdown-extensions==10.21 +pymdown-extensions==10.21.2 jinja2==3.1.6 mike==2.1.4 From 82ab8c4674c7f526bbcd9034f462b2c8d4ae0b66 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 03:04:24 +0000 Subject: [PATCH 150/315] chore(deps): bump codecov/codecov-action from 5.5.2 to 6.0.0 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.2 to 6.0.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/671740ac38dd9b0130fbe1cec585b89eea48d3de...57e3a136b779b570ffcdbf80b3bdc90e7fab3de2) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-version: 6.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .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 c7a5842db..1a2a7bbbf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -73,7 +73,7 @@ jobs: run: | pytest --random-order --cov=freqtrade --cov=freqtrade_client --cov-config=.coveragerc - - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + - uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0 if: (runner.os == 'Linux' && matrix.python-version == '3.12' && matrix.os == 'ubuntu-24.04') with: fail_ci_if_error: true From d25124021c4b903e8487e9ad675955de7ec33f08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 03:04:40 +0000 Subject: [PATCH 151/315] chore(deps): bump ccxt from 4.5.45 to 4.5.46 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.5.45 to 4.5.46. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Commits](https://github.com/ccxt/ccxt/compare/v4.5.45...v4.5.46) --- updated-dependencies: - dependency-name: ccxt dependency-version: 4.5.46 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 aa5c3ef7c..f48d5681b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ ft-pandas-ta==0.3.16 ta-lib==0.6.8 technical==1.5.4 -ccxt==4.5.45 +ccxt==4.5.46 cryptography==46.0.6 aiohttp==3.13.4 SQLAlchemy==2.0.48 From a33def834a2fbf164f111c4bf2052240f988c992 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 03:04:48 +0000 Subject: [PATCH 152/315] chore(deps): bump torch from 2.10.0 to 2.11.0 Bumps [torch](https://github.com/pytorch/pytorch) from 2.10.0 to 2.11.0. - [Release notes](https://github.com/pytorch/pytorch/releases) - [Changelog](https://github.com/pytorch/pytorch/blob/main/RELEASE.md) - [Commits](https://github.com/pytorch/pytorch/compare/v2.10.0...v2.11.0) --- updated-dependencies: - dependency-name: torch dependency-version: 2.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-freqai-rl.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai-rl.txt b/requirements-freqai-rl.txt index e2aef02ae..c1a6cc6fa 100644 --- a/requirements-freqai-rl.txt +++ b/requirements-freqai-rl.txt @@ -2,7 +2,7 @@ -r requirements-freqai.txt # Required for freqai-rl -torch==2.10.0; sys_platform != 'darwin' or platform_machine != 'x86_64' +torch==2.11.0; sys_platform != 'darwin' or platform_machine != 'x86_64' gymnasium==1.2.3 # SB3 >=2.5.0 depends on torch 2.3.0 - which implies it dropped support x86 macos stable_baselines3==2.7.1; sys_platform != 'darwin' or platform_machine != 'x86_64' From 63ac3bef01203514ad2904ac332b263d39a5026a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Apr 2026 06:38:04 +0000 Subject: [PATCH 153/315] chore(deps-dev): bump the types group with 2 updates Bumps the types group with 2 updates: [types-requests](https://github.com/python/typeshed) and [types-python-dateutil](https://github.com/python/typeshed). Updates `types-requests` from 2.32.4.20260107 to 2.33.0.20260327 - [Commits](https://github.com/python/typeshed/commits) Updates `types-python-dateutil` from 2.9.0.20260305 to 2.9.0.20260323 - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.33.0.20260327 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: types - dependency-name: types-python-dateutil dependency-version: 2.9.0.20260323 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index e67e336a8..cd6b0688c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -26,9 +26,9 @@ nbconvert==7.17.0 scipy-stubs==1.17.1.3 # keep in sync with `scipy` in `requirements-hyperopt.txt` types-cachetools==6.2.0.20260317 types-filelock==3.2.7 -types-requests==2.32.4.20260107 +types-requests==2.33.0.20260327 types-tabulate==0.10.0.20260308 -types-python-dateutil==2.9.0.20260305 +types-python-dateutil==2.9.0.20260323 pip-audit==2.10.0 # For build step in CI build==1.4.2 From e45d781630cc959d590fd0ce42d788d26f594d4e Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 6 Apr 2026 08:57:18 +0200 Subject: [PATCH 154/315] chore: bump types in pre-commit config --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 387b4cbfe..78b7eb2c9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,9 +22,9 @@ repos: additional_dependencies: - types-cachetools==6.2.0.20260317 - types-filelock==3.2.7 - - types-requests==2.32.4.20260107 + - types-requests==2.33.0.20260327 - types-tabulate==0.10.0.20260308 - - types-python-dateutil==2.9.0.20260305 + - types-python-dateutil==2.9.0.20260323 - scipy-stubs==1.17.1.3 - SQLAlchemy==2.0.48 # stages: [push] From 04990a888beafbf9066afe81385928fe5c465ad6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 6 Apr 2026 09:26:08 +0200 Subject: [PATCH 155/315] fix: improve bot cleanup methods for safer shutdown --- freqtrade/freqtradebot.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 2b4d6c8ff..24c45d048 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -216,7 +216,8 @@ class FreqtradeBot(LoggingMixin): logger.warning(f"Exception during cleanup: {e.__class__.__name__} {e}") finally: - self.strategy.ft_bot_cleanup() + if getattr(self, "strategy", None): + self.strategy.ft_bot_cleanup() if getattr(self, "rpc", None): self.rpc.cleanup() @@ -225,7 +226,8 @@ class FreqtradeBot(LoggingMixin): if getattr(self, "exchange", None): self.exchange.close() try: - Trade.commit() + if hasattr(Trade, "session"): + Trade.commit() except Exception: # Exceptions here will be happening if the db disappeared. # At which point we can no longer commit anyway. From 9582bd3cd72cb04cddf7a6d51fd93e4cab1a07e4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 6 Apr 2026 09:58:35 +0200 Subject: [PATCH 156/315] feat: don't allow re-creation of existing logs This prevents lock spam - where 100ds of identical rows can be inserted into the database --- freqtrade/persistence/pairlock.py | 5 +++++ freqtrade/persistence/pairlock_middleware.py | 12 +++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/freqtrade/persistence/pairlock.py b/freqtrade/persistence/pairlock.py index eedc7286f..21439e5f7 100644 --- a/freqtrade/persistence/pairlock.py +++ b/freqtrade/persistence/pairlock.py @@ -29,6 +29,11 @@ class PairLock(ModelBase): active: Mapped[bool] = mapped_column(nullable=False, default=True, index=True) + @property + def lock_end_time_utc(self) -> datetime: + """Lock end time with UTC timezoneinfo""" + return self.lock_end_time.replace(tzinfo=UTC) + def __repr__(self) -> str: lock_time = self.lock_time.strftime(DATETIME_PRINT_FORMAT) lock_end_time = self.lock_end_time.strftime(DATETIME_PRINT_FORMAT) diff --git a/freqtrade/persistence/pairlock_middleware.py b/freqtrade/persistence/pairlock_middleware.py index 94544928d..c7a9fca83 100644 --- a/freqtrade/persistence/pairlock_middleware.py +++ b/freqtrade/persistence/pairlock_middleware.py @@ -42,6 +42,7 @@ class PairLocks: ) -> PairLock: """ Create PairLock from now to "until". + Does not create a new lock if there is already a lock with the same Reason, side and end time. Uses database by default, unless PairLocks.use_db is set to False, in which case a list is maintained. :param pair: pair to lock. use '*' to lock all pairs @@ -50,10 +51,19 @@ class PairLocks: :param now: Current timestamp. Used to determine lock start time. :param side: Side to lock pair, can be 'long', 'short' or '*' """ + lock_end_time = timeframe_to_next_date(PairLocks.timeframe, until) + existing_locks = PairLocks.get_pair_locks(pair, now, side=side) + for lock in existing_locks: + if ( + lock.reason == reason + and lock.lock_end_time_utc == lock_end_time + and lock.side == side + ): + return lock lock = PairLock( pair=pair, lock_time=now or datetime.now(UTC), - lock_end_time=timeframe_to_next_date(PairLocks.timeframe, until), + lock_end_time=lock_end_time, reason=reason, side=side, active=True, From 6712d205785d559ce3780933843bfbe36643293e Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 6 Apr 2026 09:58:58 +0200 Subject: [PATCH 157/315] test: Add test for new pairlock deduplication --- tests/plugins/test_pairlocks.py | 29 +++++++++++++++++++++++++++++ tests/rpc/test_rpc.py | 4 ++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/tests/plugins/test_pairlocks.py b/tests/plugins/test_pairlocks.py index 678355f1c..f55c6ad88 100644 --- a/tests/plugins/test_pairlocks.py +++ b/tests/plugins/test_pairlocks.py @@ -158,3 +158,32 @@ def test_PairLocks_reason(use_db): PairLocks.reset_locks() PairLocks.use_db = True + + +@pytest.mark.parametrize("use_db", (False, True)) +@pytest.mark.usefixtures("init_persistence") +def test_PairLocks_no_duplicates(use_db, time_machine): + PairLocks.timeframe = "5m" + PairLocks.use_db = use_db + # No lock should be present + assert len(PairLocks.get_all_locks()) == 0 + time_machine.move_to("2026-01-05 20:00:05 +00:00", tick=False) + + assert PairLocks.use_db == use_db + PairLocks.lock_pair("XRP/USDT", dt_now() + timedelta(minutes=4), "TestLock1") + assert len(PairLocks.get_all_locks()) == 1 + + PairLocks.lock_pair("XRP/USDT", dt_now() + timedelta(minutes=4), "TestLock1") + assert len(PairLocks.get_all_locks()) == 1 + + # Different Reason - should create a new lock + PairLocks.lock_pair("XRP/USDT", dt_now() + timedelta(minutes=4), "TestLock2") + assert len(PairLocks.get_all_locks()) == 2 + + # Different end-time - should create a new lock + PairLocks.lock_pair("XRP/USDT", dt_now() + timedelta(minutes=5), "TestLock1") + assert len(PairLocks.get_all_locks()) == 3 + + # Different side - should create a new lock + PairLocks.lock_pair("XRP/USDT", dt_now() + timedelta(minutes=4), "TestLock1", side="long") + assert len(PairLocks.get_all_locks()) == 4 diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index fe82e9904..86084ad5f 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -1434,8 +1434,8 @@ def test_rpc_add_and_delete_lock(mocker, default_conf): pair = "ETH/BTC" rpc._rpc_add_lock(pair, datetime.now(UTC) + timedelta(minutes=4), "", "*") - rpc._rpc_add_lock(pair, datetime.now(UTC) + timedelta(minutes=5), "", "*") - rpc._rpc_add_lock(pair, datetime.now(UTC) + timedelta(minutes=10), "", "*") + rpc._rpc_add_lock(pair, datetime.now(UTC) + timedelta(minutes=20), "", "*") + rpc._rpc_add_lock(pair, datetime.now(UTC) + timedelta(minutes=50), "", "*") locks = rpc._rpc_locks() assert locks["lock_count"] == 3 From 8890cdbcdb4429d1815929809d743e46e687c0e8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 6 Apr 2026 10:05:26 +0200 Subject: [PATCH 158/315] chore: shorter wording in docstring --- freqtrade/persistence/pairlock_middleware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/persistence/pairlock_middleware.py b/freqtrade/persistence/pairlock_middleware.py index c7a9fca83..ccf302a22 100644 --- a/freqtrade/persistence/pairlock_middleware.py +++ b/freqtrade/persistence/pairlock_middleware.py @@ -42,7 +42,7 @@ class PairLocks: ) -> PairLock: """ Create PairLock from now to "until". - Does not create a new lock if there is already a lock with the same Reason, side and end time. + Doesn't create a new lock if there is already a lock with the same Reason, side and endtime. Uses database by default, unless PairLocks.use_db is set to False, in which case a list is maintained. :param pair: pair to lock. use '*' to lock all pairs From c906e146fc6fcb28c553d4fa1d0813ec2d888552 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 6 Apr 2026 10:24:41 +0200 Subject: [PATCH 159/315] test: add reset_locks to the end of the new test --- tests/plugins/test_pairlocks.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/plugins/test_pairlocks.py b/tests/plugins/test_pairlocks.py index f55c6ad88..28298ac52 100644 --- a/tests/plugins/test_pairlocks.py +++ b/tests/plugins/test_pairlocks.py @@ -187,3 +187,6 @@ def test_PairLocks_no_duplicates(use_db, time_machine): # Different side - should create a new lock PairLocks.lock_pair("XRP/USDT", dt_now() + timedelta(minutes=4), "TestLock1", side="long") assert len(PairLocks.get_all_locks()) == 4 + + PairLocks.reset_locks() + PairLocks.use_db = True From d026b67dee609ade4be2dbe852d89db5406b459b Mon Sep 17 00:00:00 2001 From: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com> Date: Tue, 7 Apr 2026 03:56:56 +0000 Subject: [PATCH 160/315] chore: update pre-commit hooks --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 78b7eb2c9..742c5dd93 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.19.1" + rev: "v1.20.0" hooks: - id: mypy exclude: build_helpers @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.15.8' + rev: 'v0.15.9' hooks: - id: ruff - id: ruff-format From 70b24fd0c1a4e8cb86e5381d45b19363bbcbc004 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 7 Apr 2026 06:44:25 +0200 Subject: [PATCH 161/315] test: reset pairlocks pior to running test --- tests/plugins/test_pairlocks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/plugins/test_pairlocks.py b/tests/plugins/test_pairlocks.py index 28298ac52..729691bf5 100644 --- a/tests/plugins/test_pairlocks.py +++ b/tests/plugins/test_pairlocks.py @@ -165,6 +165,7 @@ def test_PairLocks_reason(use_db): def test_PairLocks_no_duplicates(use_db, time_machine): PairLocks.timeframe = "5m" PairLocks.use_db = use_db + PairLocks.reset_locks() # No lock should be present assert len(PairLocks.get_all_locks()) == 0 time_machine.move_to("2026-01-05 20:00:05 +00:00", tick=False) From 02125f18830fea959a8fb5975e6d44a8022a00ab Mon Sep 17 00:00:00 2001 From: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com> Date: Thu, 9 Apr 2026 04:18:20 +0000 Subject: [PATCH 162/315] chore: update binance leverage tiers --- .../exchange/binance_leverage_tiers.json | 1214 ++++++++++++----- 1 file changed, 868 insertions(+), 346 deletions(-) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 6ff17bdf9..58f8b3286 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -3687,6 +3687,110 @@ } } ], + "AAPL/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "AAPL/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 1, + "initialLeverage": 10, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.05, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "AAPL/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 2, + "initialLeverage": 5, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.1, + "cum": 250.0 + } + }, + { + "tier": 3.0, + "symbol": "AAPL/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 3, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 10000, + "maintMarginRatio": 0.125, + "cum": 500.0 + } + }, + { + "tier": 4.0, + "symbol": "AAPL/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 4, + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 4670.0 + } + }, + { + "tier": 5.0, + "symbol": "AAPL/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 5, + "initialLeverage": 2, + "notionalCap": 8000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 46320.0 + } + }, + { + "tier": 6.0, + "symbol": "AAPL/USDT:USDT", + "currency": "USDT", + "minNotional": 8000000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 6, + "initialLeverage": 1, + "notionalCap": 15000000, + "notionalFloor": 8000000, + "maintMarginRatio": 0.5, + "cum": 2046320.0 + } + } + ], "AAVE/USDC:USDC": [ { "tier": 1.0, @@ -30797,15 +30901,15 @@ "symbol": "DRIFT/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.04, "maxLeverage": 20.0, "info": { "bracket": 1, "initialLeverage": 20, - "notionalCap": 10000, + "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.025, + "maintMarginRatio": 0.04, "cum": 0.0 } }, @@ -30813,58 +30917,58 @@ "tier": 2.0, "symbol": "DRIFT/USDT:USDT", "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 20000.0, + "minNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": 2, "initialLeverage": 10, - "notionalCap": 20000, - "notionalFloor": 10000, + "notionalCap": 10000, + "notionalFloor": 5000, "maintMarginRatio": 0.05, - "cum": 250.0 + "cum": 50.0 } }, { "tier": 3.0, "symbol": "DRIFT/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 50000.0, + "minNotional": 10000.0, + "maxNotional": 30000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": 3, "initialLeverage": 5, - "notionalCap": 50000, - "notionalFloor": 20000, + "notionalCap": 30000, + "notionalFloor": 10000, "maintMarginRatio": 0.1, - "cum": 1250.0 + "cum": 550.0 } }, { "tier": 4.0, "symbol": "DRIFT/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 250000.0, + "minNotional": 30000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": 4, "initialLeverage": 4, - "notionalCap": 250000, - "notionalFloor": 50000, + "notionalCap": 200000, + "notionalFloor": 30000, "maintMarginRatio": 0.125, - "cum": 2500.0 + "cum": 1300.0 } }, { "tier": 5.0, "symbol": "DRIFT/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 200000.0, "maxNotional": 500000.0, "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, @@ -30872,9 +30976,9 @@ "bracket": 5, "initialLeverage": 3, "notionalCap": 500000, - "notionalFloor": 250000, + "notionalFloor": 200000, "maintMarginRatio": 0.1667, - "cum": 12925.0 + "cum": 9640.0 } }, { @@ -30891,7 +30995,7 @@ "notionalCap": 600000, "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 54575.0 + "cum": 51290.0 } }, { @@ -30908,7 +31012,7 @@ "notionalCap": 650000, "notionalFloor": 600000, "maintMarginRatio": 0.5, - "cum": 204575.0 + "cum": 201290.0 } } ], @@ -37326,13 +37430,13 @@ "symbol": "FIO/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 2500000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": 5, "initialLeverage": 2, - "notionalCap": 2500000, + "notionalCap": 500000, "notionalFloor": 250000, "maintMarginRatio": 0.25, "cum": 26745.0 @@ -37342,17 +37446,17 @@ "tier": 6.0, "symbol": "FIO/USDT:USDT", "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 500000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": 6, "initialLeverage": 1, - "notionalCap": 5000000, - "notionalFloor": 2500000, + "notionalCap": 800000, + "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 651745.0 + "cum": 151745.0 } } ], @@ -39190,13 +39294,13 @@ "symbol": "FUN/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 2500000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": 6, "initialLeverage": 2, - "notionalCap": 2500000, + "notionalCap": 500000, "notionalFloor": 250000, "maintMarginRatio": 0.25, "cum": 27120.0 @@ -39206,17 +39310,17 @@ "tier": 7.0, "symbol": "FUN/USDT:USDT", "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 500000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": 7, "initialLeverage": 1, - "notionalCap": 5000000, - "notionalFloor": 2500000, + "notionalCap": 800000, + "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 652120.0 + "cum": 152120.0 } } ], @@ -43521,13 +43625,13 @@ "symbol": "HIPPO/USDT:USDT", "currency": "USDT", "minNotional": 100000.0, - "maxNotional": 1000000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { "bracket": 4, "initialLeverage": 3, - "notionalCap": 1000000, + "notionalCap": 500000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, "cum": 5920.0 @@ -43537,34 +43641,34 @@ "tier": 5.0, "symbol": "HIPPO/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2500000.0, + "minNotional": 500000.0, + "maxNotional": 600000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": 5, "initialLeverage": 2, - "notionalCap": 2500000, - "notionalFloor": 1000000, + "notionalCap": 600000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 89220.0 + "cum": 47570.0 } }, { "tier": 6.0, "symbol": "HIPPO/USDT:USDT", "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 600000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": 6, "initialLeverage": 1, - "notionalCap": 5000000, - "notionalFloor": 2500000, + "notionalCap": 800000, + "notionalFloor": 600000, "maintMarginRatio": 0.5, - "cum": 714220.0 + "cum": 197570.0 } } ], @@ -55084,13 +55188,13 @@ "symbol": "M/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 40000.0, + "maxNotional": 20000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": 3, "initialLeverage": 10, - "notionalCap": 40000, + "notionalCap": 20000, "notionalFloor": 10000, "maintMarginRatio": 0.05, "cum": 300.0 @@ -55100,24 +55204,24 @@ "tier": 4.0, "symbol": "M/USDT:USDT", "currency": "USDT", - "minNotional": 40000.0, - "maxNotional": 100000.0, + "minNotional": 20000.0, + "maxNotional": 80000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": 4, "initialLeverage": 5, - "notionalCap": 100000, - "notionalFloor": 40000, + "notionalCap": 80000, + "notionalFloor": 20000, "maintMarginRatio": 0.1, - "cum": 2300.0 + "cum": 1300.0 } }, { "tier": 5.0, "symbol": "M/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, + "minNotional": 80000.0, "maxNotional": 200000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, @@ -55125,9 +55229,9 @@ "bracket": 5, "initialLeverage": 4, "notionalCap": 200000, - "notionalFloor": 100000, + "notionalFloor": 80000, "maintMarginRatio": 0.125, - "cum": 4800.0 + "cum": 3300.0 } }, { @@ -55144,7 +55248,7 @@ "notionalCap": 500000, "notionalFloor": 200000, "maintMarginRatio": 0.1667, - "cum": 13140.0 + "cum": 11640.0 } }, { @@ -55161,7 +55265,7 @@ "notionalCap": 2500000, "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 54790.0 + "cum": 53290.0 } }, { @@ -55178,7 +55282,7 @@ "notionalCap": 3000000, "notionalFloor": 2500000, "maintMarginRatio": 0.5, - "cum": 679790.0 + "cum": 678290.0 } } ], @@ -60267,6 +60371,110 @@ } } ], + "MU/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "MU/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 1, + "initialLeverage": 10, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.05, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "MU/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 2, + "initialLeverage": 5, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.1, + "cum": 250.0 + } + }, + { + "tier": 3.0, + "symbol": "MU/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 3, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 10000, + "maintMarginRatio": 0.125, + "cum": 500.0 + } + }, + { + "tier": 4.0, + "symbol": "MU/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 4, + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 4670.0 + } + }, + { + "tier": 5.0, + "symbol": "MU/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 5, + "initialLeverage": 2, + "notionalCap": 8000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 46320.0 + } + }, + { + "tier": 6.0, + "symbol": "MU/USDT:USDT", + "currency": "USDT", + "minNotional": 8000000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 6, + "initialLeverage": 1, + "notionalCap": 15000000, + "notionalFloor": 8000000, + "maintMarginRatio": 0.5, + "cum": 2046320.0 + } + } + ], "MUBARAK/USDT:USDT": [ { "tier": 1.0, @@ -63982,15 +64190,15 @@ "symbol": "OL/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 1, - "initialLeverage": 20, - "notionalCap": 5000, + "initialLeverage": 10, + "notionalCap": 10000, "notionalFloor": 0, - "maintMarginRatio": 0.025, + "maintMarginRatio": 0.05, "cum": 0.0 } }, @@ -63998,38 +64206,21 @@ "tier": 2.0, "symbol": "OL/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": 2, - "initialLeverage": 10, - "notionalCap": 10000, - "notionalFloor": 5000, - "maintMarginRatio": 0.05, - "cum": 125.0 - } - }, - { - "tier": 3.0, - "symbol": "OL/USDT:USDT", - "currency": "USDT", "minNotional": 10000.0, "maxNotional": 60000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": 3, + "bracket": 2, "initialLeverage": 5, "notionalCap": 60000, "notionalFloor": 10000, "maintMarginRatio": 0.1, - "cum": 625.0 + "cum": 500.0 } }, { - "tier": 4.0, + "tier": 3.0, "symbol": "OL/USDT:USDT", "currency": "USDT", "minNotional": 60000.0, @@ -64037,16 +64228,16 @@ "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 4, + "bracket": 3, "initialLeverage": 4, "notionalCap": 70000, "notionalFloor": 60000, "maintMarginRatio": 0.125, - "cum": 2125.0 + "cum": 2000.0 } }, { - "tier": 5.0, + "tier": 4.0, "symbol": "OL/USDT:USDT", "currency": "USDT", "minNotional": 70000.0, @@ -64054,16 +64245,16 @@ "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 5, + "bracket": 4, "initialLeverage": 3, "notionalCap": 250000, "notionalFloor": 70000, "maintMarginRatio": 0.1667, - "cum": 5044.0 + "cum": 4919.0 } }, { - "tier": 6.0, + "tier": 5.0, "symbol": "OL/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, @@ -64071,16 +64262,16 @@ "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 6, + "bracket": 5, "initialLeverage": 2, "notionalCap": 500000, "notionalFloor": 250000, "maintMarginRatio": 0.25, - "cum": 25869.0 + "cum": 25744.0 } }, { - "tier": 7.0, + "tier": 6.0, "symbol": "OL/USDT:USDT", "currency": "USDT", "minNotional": 500000.0, @@ -64088,12 +64279,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 7, + "bracket": 6, "initialLeverage": 1, "notionalCap": 800000, "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 150869.0 + "cum": 150744.0 } } ], @@ -66584,14 +66775,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.035, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.035, "cum": 0.0 } }, @@ -66600,67 +66791,67 @@ "symbol": "OXT/USDT:USDT", "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 15000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 2, - "initialLeverage": 20, - "notionalCap": 10000, + "initialLeverage": 10, + "notionalCap": 15000, "notionalFloor": 5000, - "maintMarginRatio": 0.025, - "cum": 50.0 + "maintMarginRatio": 0.05, + "cum": 75.0 } }, { "tier": 3.0, "symbol": "OXT/USDT:USDT", "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 15000.0, + "maxNotional": 30000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 3, - "initialLeverage": 10, - "notionalCap": 20000, - "notionalFloor": 10000, - "maintMarginRatio": 0.05, - "cum": 300.0 + "initialLeverage": 5, + "notionalCap": 30000, + "notionalFloor": 15000, + "maintMarginRatio": 0.1, + "cum": 825.0 } }, { "tier": 4.0, "symbol": "OXT/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 30000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 4, - "initialLeverage": 5, - "notionalCap": 50000, - "notionalFloor": 20000, - "maintMarginRatio": 0.1, - "cum": 1300.0 + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 30000, + "maintMarginRatio": 0.125, + "cum": 1575.0 } }, { "tier": 5.0, "symbol": "OXT/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, + "minNotional": 100000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 5, - "initialLeverage": 4, + "initialLeverage": 3, "notionalCap": 250000, - "notionalFloor": 50000, - "maintMarginRatio": 0.125, - "cum": 2550.0 + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 5745.0 } }, { @@ -66669,15 +66860,15 @@ "currency": "USDT", "minNotional": 250000.0, "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 6, - "initialLeverage": 3, + "initialLeverage": 2, "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1667, - "cum": 12975.0 + "maintMarginRatio": 0.25, + "cum": 26570.0 } }, { @@ -66685,33 +66876,16 @@ "symbol": "OXT/USDT:USDT", "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 7500000, - "notionalFloor": 500000, - "maintMarginRatio": 0.25, - "cum": 54625.0 - } - }, - { - "tier": 8.0, - "symbol": "OXT/USDT:USDT", - "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 12500000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 7, "initialLeverage": 1, - "notionalCap": 12500000, - "notionalFloor": 7500000, + "notionalCap": 800000, + "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 1929625.0 + "cum": 151570.0 } } ], @@ -71203,15 +71377,15 @@ "symbol": "PUFFER/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 1, - "initialLeverage": 25, - "notionalCap": 5000, + "initialLeverage": 10, + "notionalCap": 10000, "notionalFloor": 0, - "maintMarginRatio": 0.02, + "maintMarginRatio": 0.05, "cum": 0.0 } }, @@ -71219,119 +71393,85 @@ "tier": 2.0, "symbol": "PUFFER/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 10000.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 2, - "initialLeverage": 20, - "notionalCap": 10000, - "notionalFloor": 5000, - "maintMarginRatio": 0.025, - "cum": 25.0 + "initialLeverage": 5, + "notionalCap": 40000, + "notionalFloor": 10000, + "maintMarginRatio": 0.1, + "cum": 500.0 } }, { "tier": 3.0, "symbol": "PUFFER/USDT:USDT", "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 40000.0, + "maxNotional": 80000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 3, - "initialLeverage": 10, - "notionalCap": 25000, - "notionalFloor": 10000, - "maintMarginRatio": 0.05, - "cum": 275.0 + "initialLeverage": 4, + "notionalCap": 80000, + "notionalFloor": 40000, + "maintMarginRatio": 0.125, + "cum": 1500.0 } }, { "tier": 4.0, "symbol": "PUFFER/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 80000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 4, - "initialLeverage": 5, - "notionalCap": 50000, - "notionalFloor": 25000, - "maintMarginRatio": 0.1, - "cum": 1525.0 + "initialLeverage": 3, + "notionalCap": 250000, + "notionalFloor": 80000, + "maintMarginRatio": 0.1667, + "cum": 4836.0 } }, { "tier": 5.0, "symbol": "PUFFER/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 5, - "initialLeverage": 4, - "notionalCap": 100000, - "notionalFloor": 50000, - "maintMarginRatio": 0.125, - "cum": 2775.0 + "initialLeverage": 2, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.25, + "cum": 25661.0 } }, { "tier": 6.0, "symbol": "PUFFER/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 6, - "initialLeverage": 3, - "notionalCap": 250000, - "notionalFloor": 100000, - "maintMarginRatio": 0.1667, - "cum": 6945.0 - } - }, - { - "tier": 7.0, - "symbol": "PUFFER/USDT:USDT", - "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 2500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 2500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.25, - "cum": 27770.0 - } - }, - { - "tier": 8.0, - "symbol": "PUFFER/USDT:USDT", - "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 500000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 6, "initialLeverage": 1, - "notionalCap": 5000000, - "notionalFloor": 2500000, + "notionalCap": 800000, + "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 652770.0 + "cum": 150661.0 } } ], @@ -72214,6 +72354,110 @@ } } ], + "QQQ/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "QQQ/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 1, + "initialLeverage": 10, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.05, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "QQQ/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 2, + "initialLeverage": 5, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.1, + "cum": 250.0 + } + }, + { + "tier": 3.0, + "symbol": "QQQ/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 3, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 10000, + "maintMarginRatio": 0.125, + "cum": 500.0 + } + }, + { + "tier": 4.0, + "symbol": "QQQ/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 4, + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 4670.0 + } + }, + { + "tier": 5.0, + "symbol": "QQQ/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 5, + "initialLeverage": 2, + "notionalCap": 8000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 46320.0 + } + }, + { + "tier": 6.0, + "symbol": "QQQ/USDT:USDT", + "currency": "USDT", + "minNotional": 8000000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 6, + "initialLeverage": 1, + "notionalCap": 15000000, + "notionalFloor": 8000000, + "maintMarginRatio": 0.5, + "cum": 2046320.0 + } + } + ], "QTUM/USDT:USDT": [ { "tier": 1.0, @@ -74825,15 +75069,15 @@ "symbol": "RLS/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 1, - "initialLeverage": 20, - "notionalCap": 5000, + "initialLeverage": 10, + "notionalCap": 10000, "notionalFloor": 0, - "maintMarginRatio": 0.025, + "maintMarginRatio": 0.05, "cum": 0.0 } }, @@ -74841,38 +75085,21 @@ "tier": 2.0, "symbol": "RLS/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": 2, - "initialLeverage": 10, - "notionalCap": 10000, - "notionalFloor": 5000, - "maintMarginRatio": 0.05, - "cum": 125.0 - } - }, - { - "tier": 3.0, - "symbol": "RLS/USDT:USDT", - "currency": "USDT", "minNotional": 10000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": 3, + "bracket": 2, "initialLeverage": 5, "notionalCap": 50000, "notionalFloor": 10000, "maintMarginRatio": 0.1, - "cum": 625.0 + "cum": 500.0 } }, { - "tier": 4.0, + "tier": 3.0, "symbol": "RLS/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, @@ -74880,16 +75107,16 @@ "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 4, + "bracket": 3, "initialLeverage": 4, "notionalCap": 100000, "notionalFloor": 50000, "maintMarginRatio": 0.125, - "cum": 1875.0 + "cum": 1750.0 } }, { - "tier": 5.0, + "tier": 4.0, "symbol": "RLS/USDT:USDT", "currency": "USDT", "minNotional": 100000.0, @@ -74897,16 +75124,16 @@ "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 5, + "bracket": 4, "initialLeverage": 3, "notionalCap": 250000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 6045.0 + "cum": 5920.0 } }, { - "tier": 6.0, + "tier": 5.0, "symbol": "RLS/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, @@ -74914,16 +75141,16 @@ "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 6, + "bracket": 5, "initialLeverage": 2, "notionalCap": 500000, "notionalFloor": 250000, "maintMarginRatio": 0.25, - "cum": 26870.0 + "cum": 26745.0 } }, { - "tier": 7.0, + "tier": 6.0, "symbol": "RLS/USDT:USDT", "currency": "USDT", "minNotional": 500000.0, @@ -74931,12 +75158,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 7, + "bracket": 6, "initialLeverage": 1, "notionalCap": 800000, "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 151870.0 + "cum": 151745.0 } } ], @@ -78289,14 +78516,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -78305,15 +78532,15 @@ "symbol": "SIGN/USDT:USDT", "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": 2, - "initialLeverage": 50, - "notionalCap": 10000, + "initialLeverage": 25, + "notionalCap": 25000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.02, "cum": 25.0 } }, @@ -78321,38 +78548,21 @@ "tier": 3.0, "symbol": "SIGN/USDT:USDT", "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, - "info": { - "bracket": 3, - "initialLeverage": 25, - "notionalCap": 25000, - "notionalFloor": 10000, - "maintMarginRatio": 0.02, - "cum": 75.0 - } - }, - { - "tier": 4.0, - "symbol": "SIGN/USDT:USDT", - "currency": "USDT", "minNotional": 25000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { - "bracket": 4, + "bracket": 3, "initialLeverage": 20, "notionalCap": 50000, "notionalFloor": 25000, "maintMarginRatio": 0.025, - "cum": 200.0 + "cum": 150.0 } }, { - "tier": 5.0, + "tier": 4.0, "symbol": "SIGN/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, @@ -78360,16 +78570,16 @@ "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": 5, + "bracket": 4, "initialLeverage": 10, "notionalCap": 125000, "notionalFloor": 50000, "maintMarginRatio": 0.05, - "cum": 1450.0 + "cum": 1400.0 } }, { - "tier": 6.0, + "tier": 5.0, "symbol": "SIGN/USDT:USDT", "currency": "USDT", "minNotional": 125000.0, @@ -78377,16 +78587,16 @@ "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": 6, + "bracket": 5, "initialLeverage": 5, "notionalCap": 250000, "notionalFloor": 125000, "maintMarginRatio": 0.1, - "cum": 7700.0 + "cum": 7650.0 } }, { - "tier": 7.0, + "tier": 6.0, "symbol": "SIGN/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, @@ -78394,16 +78604,16 @@ "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 7, + "bracket": 6, "initialLeverage": 4, "notionalCap": 500000, "notionalFloor": 250000, "maintMarginRatio": 0.125, - "cum": 13950.0 + "cum": 13900.0 } }, { - "tier": 8.0, + "tier": 7.0, "symbol": "SIGN/USDT:USDT", "currency": "USDT", "minNotional": 500000.0, @@ -78411,46 +78621,46 @@ "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 8, + "bracket": 7, "initialLeverage": 3, "notionalCap": 1000000, "notionalFloor": 500000, "maintMarginRatio": 0.1667, - "cum": 34800.0 + "cum": 34750.0 + } + }, + { + "tier": 8.0, + "symbol": "SIGN/USDT:USDT", + "currency": "USDT", + "minNotional": 1000000.0, + "maxNotional": 2000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 8, + "initialLeverage": 2, + "notionalCap": 2000000, + "notionalFloor": 1000000, + "maintMarginRatio": 0.25, + "cum": 118050.0 } }, { "tier": 9.0, "symbol": "SIGN/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 9, - "initialLeverage": 2, - "notionalCap": 7500000, - "notionalFloor": 1000000, - "maintMarginRatio": 0.25, - "cum": 118100.0 - } - }, - { - "tier": 10.0, - "symbol": "SIGN/USDT:USDT", - "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 12500000.0, + "minNotional": 2000000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 10, + "bracket": 9, "initialLeverage": 1, - "notionalCap": 12500000, - "notionalFloor": 7500000, + "notionalCap": 3000000, + "notionalFloor": 2000000, "maintMarginRatio": 0.5, - "cum": 1993100.0 + "cum": 618050.0 } } ], @@ -79490,6 +79700,110 @@ } } ], + "SNDK/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "SNDK/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 1, + "initialLeverage": 10, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.05, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "SNDK/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 2, + "initialLeverage": 5, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.1, + "cum": 250.0 + } + }, + { + "tier": 3.0, + "symbol": "SNDK/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 3, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 10000, + "maintMarginRatio": 0.125, + "cum": 500.0 + } + }, + { + "tier": 4.0, + "symbol": "SNDK/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 4, + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 4670.0 + } + }, + { + "tier": 5.0, + "symbol": "SNDK/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 5, + "initialLeverage": 2, + "notionalCap": 8000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 46320.0 + } + }, + { + "tier": 6.0, + "symbol": "SNDK/USDT:USDT", + "currency": "USDT", + "minNotional": 8000000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 6, + "initialLeverage": 1, + "notionalCap": 15000000, + "notionalFloor": 8000000, + "maintMarginRatio": 0.5, + "cum": 2046320.0 + } + } + ], "SNT/USDT:USDT": [ { "tier": 1.0, @@ -81626,6 +81940,110 @@ } } ], + "SPY/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "SPY/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 1, + "initialLeverage": 10, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.05, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "SPY/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 2, + "initialLeverage": 5, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.1, + "cum": 250.0 + } + }, + { + "tier": 3.0, + "symbol": "SPY/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 3, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 10000, + "maintMarginRatio": 0.125, + "cum": 500.0 + } + }, + { + "tier": 4.0, + "symbol": "SPY/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 4, + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 4670.0 + } + }, + { + "tier": 5.0, + "symbol": "SPY/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 5, + "initialLeverage": 2, + "notionalCap": 8000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 46320.0 + } + }, + { + "tier": 6.0, + "symbol": "SPY/USDT:USDT", + "currency": "USDT", + "minNotional": 8000000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 6, + "initialLeverage": 1, + "notionalCap": 15000000, + "notionalFloor": 8000000, + "maintMarginRatio": 0.5, + "cum": 2046320.0 + } + } + ], "SQD/USDT:USDT": [ { "tier": 1.0, @@ -89403,6 +89821,110 @@ } } ], + "TSM/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "TSM/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 1, + "initialLeverage": 10, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.05, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "TSM/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 2, + "initialLeverage": 5, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.1, + "cum": 250.0 + } + }, + { + "tier": 3.0, + "symbol": "TSM/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 3, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 10000, + "maintMarginRatio": 0.125, + "cum": 500.0 + } + }, + { + "tier": 4.0, + "symbol": "TSM/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 4, + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 4670.0 + } + }, + { + "tier": 5.0, + "symbol": "TSM/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 5, + "initialLeverage": 2, + "notionalCap": 8000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 46320.0 + } + }, + { + "tier": 6.0, + "symbol": "TSM/USDT:USDT", + "currency": "USDT", + "minNotional": 8000000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 6, + "initialLeverage": 1, + "notionalCap": 15000000, + "notionalFloor": 8000000, + "maintMarginRatio": 0.5, + "cum": 2046320.0 + } + } + ], "TST/USDT:USDT": [ { "tier": 1.0, From 6a5fb9af42061f1fbbd4adb4c8fbf0f735ed5b3f Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 9 Apr 2026 06:32:00 +0200 Subject: [PATCH 163/315] chore: remove aiohttp shorter exclude-newer exception --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 2cdc5b214..fd23460bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -222,7 +222,6 @@ exclude-newer = "1 week" [tool.uv.exclude-newer-package] ccxt = false -aiohttp = "5 days" [tool.ruff] line-length = 100 From 86b8eae34474d2be26af5e9caab05faed12f476a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 6 Apr 2026 20:07:59 +0200 Subject: [PATCH 164/315] feat: add supports_demo_trading ft_has key --- freqtrade/exchange/bybit.py | 3 +++ freqtrade/exchange/exchange_types.py | 2 ++ 2 files changed, 5 insertions(+) diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py index 0184c6c42..bd50b232b 100644 --- a/freqtrade/exchange/bybit.py +++ b/freqtrade/exchange/bybit.py @@ -35,6 +35,9 @@ class Bybit(Exchange): # TODO: Can be removed once bybit fully forces all accounts to unified mode. "fetchOrder": False, }, + # Demo trading + # https://learn.bybit.com/en/bybit-guide/how-to-use-bybit-demo-trading + "supports_demo_trading": True, } _ft_has_futures: FtHas = { "ohlcv_has_history": True, diff --git a/freqtrade/exchange/exchange_types.py b/freqtrade/exchange/exchange_types.py index 842bc7c14..8cc9ca5b7 100644 --- a/freqtrade/exchange/exchange_types.py +++ b/freqtrade/exchange/exchange_types.py @@ -67,6 +67,8 @@ class FtHas(TypedDict, total=False): # Delisting check has_delisting: bool + # Demo mode - this is not sandbox but an exchange-provided demo mode. + supports_demo_trading: bool class Ticker(TypedDict): From 98fd645df6e7d3853001fc5ca406151d818e44f6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 6 Apr 2026 20:13:27 +0200 Subject: [PATCH 165/315] feat: enable config validation for demo_trading --- freqtrade/exchange/exchange.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 54d2bde51..1d3bd86d6 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -365,6 +365,7 @@ class Exchange: self.validate_pricing(config["exit_pricing"]) self.validate_pricing(config["entry_pricing"]) self.validate_orderflow(config["exchange"]) + self.validate_demo_trading(config["exchange"]) self.validate_freqai(config) self._set_startup_candle_count(config) @@ -870,6 +871,15 @@ class Exchange: "fetching historic OHLCV data, otherwise freqAI will not work." ) + def validate_demo_trading(self, exchange_conf: dict) -> None: + """Validate demo trading configuration + Prevents accidental configuration with wrong expectations. + """ + if exchange_conf.get("demo_trading", False) and not self.get_option( + "supports_demo_trading" + ): + raise ConfigurationError(f"Demo trading is not supported for {self.name}.") + def validate_required_startup_candles(self, startup_candles: int, timeframe: str) -> int: """ Checks if required startup_candles is more than ohlcv_candle_limit(). From e7f9059ff44433943301ce19b7ce39c7cb6bbb0e Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 6 Apr 2026 20:13:32 +0200 Subject: [PATCH 166/315] test: add test for demo trading validation --- tests/exchange/test_exchange.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 7458a4a5b..cfc6f7de7 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -348,6 +348,20 @@ def test_validate_freqai_compat(default_conf, mocker, caplog): ex.validate_freqai(default_conf) +def test_validate_demo_trading(default_conf_usdt, mocker, caplog): + caplog.set_level(logging.INFO) + # Test - nothing enabled so nothing happens + ex = get_patched_exchange(mocker, default_conf_usdt, exchange="kraken") + ex.validate_demo_trading(default_conf_usdt["exchange"]) + + default_conf_usdt["exchange"]["demo_trading"] = True + with pytest.raises(ConfigurationError, match=r"Demo trading is not supported for .*"): + ex.validate_demo_trading(default_conf_usdt["exchange"]) + + ex_bybit = get_patched_exchange(mocker, default_conf_usdt, exchange="bybit") + ex_bybit.validate_demo_trading(default_conf_usdt["exchange"]) + + @pytest.mark.parametrize( "price,precision_mode,precision,expected", [ From 1ac68d61616060de0dc03aae857a56ffe98c660e Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 7 Apr 2026 07:00:59 +0200 Subject: [PATCH 167/315] feat: show message when enabling demo trading mode --- freqtrade/exchange/exchange.py | 9 +++++---- tests/exchange/test_exchange.py | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 1d3bd86d6..076357577 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -875,10 +875,11 @@ class Exchange: """Validate demo trading configuration Prevents accidental configuration with wrong expectations. """ - if exchange_conf.get("demo_trading", False) and not self.get_option( - "supports_demo_trading" - ): - raise ConfigurationError(f"Demo trading is not supported for {self.name}.") + if exchange_conf.get("demo_trading", False): + if not self.get_option("supports_demo_trading"): + raise ConfigurationError(f"Demo trading is not supported for {self.name}.") + else: + logger.info(f"Demo trading enabled for {self.name}") def validate_required_startup_candles(self, startup_candles: int, timeframe: str) -> int: """ diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index cfc6f7de7..a9200f926 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -349,7 +349,6 @@ def test_validate_freqai_compat(default_conf, mocker, caplog): def test_validate_demo_trading(default_conf_usdt, mocker, caplog): - caplog.set_level(logging.INFO) # Test - nothing enabled so nothing happens ex = get_patched_exchange(mocker, default_conf_usdt, exchange="kraken") ex.validate_demo_trading(default_conf_usdt["exchange"]) @@ -358,8 +357,11 @@ def test_validate_demo_trading(default_conf_usdt, mocker, caplog): with pytest.raises(ConfigurationError, match=r"Demo trading is not supported for .*"): ex.validate_demo_trading(default_conf_usdt["exchange"]) + msg = r"Demo trading enabled for .*" + assert not log_has_re(msg, caplog) ex_bybit = get_patched_exchange(mocker, default_conf_usdt, exchange="bybit") ex_bybit.validate_demo_trading(default_conf_usdt["exchange"]) + assert log_has_re(msg, caplog) @pytest.mark.parametrize( From d8e0d41001259d6a911e7142ea143072ccf9cbaf Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 7 Apr 2026 07:14:09 +0200 Subject: [PATCH 168/315] feat: clearly highlight if bot is starting in demo mode --- freqtrade/rpc/rpc_manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index 72f88af29..3c9ee89ec 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -114,6 +114,8 @@ class RPCManager: trailing_stop = config["trailing_stop"] timeframe = config["timeframe"] exchange_name = config["exchange"]["name"] + if config["exchange"].get("demo_trading"): + exchange_name += " (demo trading)" strategy_name = config.get("strategy", "") pos_adjust_enabled = "On" if config["position_adjustment_enable"] else "Off" self.send_msg( From 08f0f28e90ce69bf819f0b8155deb121c343d629 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 7 Apr 2026 07:19:33 +0200 Subject: [PATCH 169/315] feat: enable demo trading mode for ccxt --- freqtrade/exchange/exchange.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 076357577..6eec2beb7 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -419,6 +419,9 @@ class Exchange: except ccxt.BaseError as e: raise OperationalException(f"Initialization of ccxt failed. Reason: {e}") from e + if self.get_option("supports_demo_trading") and exchange_config.get("demo_trading", False): + api.enable_demo_trading(True) + return api @property From 39569df42a12d185542e6973061d7a847bb8d742 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 8 Apr 2026 06:46:20 +0200 Subject: [PATCH 170/315] feat: expose demo mode via show_config --- freqtrade/rpc/api_server/api_schemas.py | 1 + freqtrade/rpc/rpc.py | 1 + freqtrade/rpc/telegram.py | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 722438ae3..5f4af98d1 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -255,6 +255,7 @@ class ShowConfig(BaseModel): timeframe_ms: int timeframe_min: int exchange: str + demo_trading: bool strategy: str | None = None force_entry_enable: bool exit_pricing: dict[str, Any] diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 0c5f46f01..446a474ef 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -176,6 +176,7 @@ class RPC: timeframe_to_minutes(config["timeframe"]) if "timeframe" in config else 0 ), "exchange": config["exchange"]["name"], + "demo_trading": config["exchange"].get("demo_trading", False), "strategy": config["strategy"], "force_entry_enable": config.get("force_entry_enable", False), "exit_pricing": config.get("exit_pricing", {}), diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 03486e3d3..3d7cf1fb3 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -2049,7 +2049,7 @@ class Telegram(RPCHandler): await self._send_msg( f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n" - f"*Exchange:* `{val['exchange']}`\n" + f"*Exchange:* `{val['exchange']}{' (Demo)' if val['demo_trading'] else ''}`\n" f"*Market: * `{val['trading_mode']}`\n" f"*Stake per trade:* `{val['stake_amount']} {val['stake_currency']}`\n" f"*Max open Trades:* `{val['max_open_trades']}`\n" From f56ea7a6cadd340f4455865eebdd567fe721f66e Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 8 Apr 2026 06:49:49 +0200 Subject: [PATCH 171/315] feat: validate demo-trading and dry-run incompatibility --- freqtrade/configuration/config_validation.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/freqtrade/configuration/config_validation.py b/freqtrade/configuration/config_validation.py index 9e9f0ada9..1c22c2bdd 100644 --- a/freqtrade/configuration/config_validation.py +++ b/freqtrade/configuration/config_validation.py @@ -92,6 +92,7 @@ def validate_config_consistency(conf: dict[str, Any], *, preliminary: bool = Fal _validate_consumers(conf) validate_migrated_strategy_settings(conf) _validate_orderflow(conf) + _validate_demo_trading(conf) # validate configuration before returning logger.info("Validating configuration ...") @@ -413,6 +414,11 @@ def _validate_orderflow(conf: dict[str, Any]) -> None: ) +def _validate_demo_trading(conf: dict[str, Any]) -> None: + if conf.get("exchange", {}).get("demo_trading", False) and conf.get("dry_run", False): + raise ConfigurationError("Demo trading cannot be used together with dry_run.") + + def _strategy_settings(conf: dict[str, Any]) -> None: process_deprecated_setting(conf, None, "use_sell_signal", None, "use_exit_signal") process_deprecated_setting(conf, None, "sell_profit_only", None, "exit_profit_only") From 8b204c75cb3375501034f90994fd2e17af82ff4d Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 8 Apr 2026 06:50:48 +0200 Subject: [PATCH 172/315] test: test validate_demo / dry-run combo --- tests/test_configuration.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_configuration.py b/tests/test_configuration.py index ba54e7b52..e58a7176a 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -1045,6 +1045,22 @@ def test__validate_orderflow(default_conf) -> None: validate_config_consistency(conf) +def test__validate_demo_trading(default_conf_usdt) -> None: + conf = deepcopy(default_conf_usdt) + validate_config_consistency(conf) + # explicitly set dry-run to clarify intent + conf["dry_run"] = True + conf["exchange"]["demo_trading"] = True + + with pytest.raises( + ConfigurationError, + match=r"Demo trading cannot be used together with dry_run\.", + ): + validate_config_consistency(conf) + conf["dry_run"] = False + validate_config_consistency(conf) + + def test_validate_edge_removal(default_conf): default_conf["edge"] = { "enabled": True, From 36b427cd3b98a6da7cd9ede5b1a9690049b168bb Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 8 Apr 2026 18:34:35 +0200 Subject: [PATCH 173/315] feat: be clear on the use of demo exchanges --- freqtrade/exchange/exchange.py | 6 +++--- tests/exchange/test_exchange.py | 21 +++++++++++++++++++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 6eec2beb7..61acdf510 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -249,7 +249,7 @@ class Exchange: # Holds all open sell orders for dry_run self._dry_run_open_orders: dict[str, Any] = {} - + self._is_demo_trading = exchange_conf.get("demo_trading", False) if self._config["dry_run"]: logger.info("Instance is running with dry_run enabled") logger.info(f"Using CCXT {ccxt.__version__}") @@ -437,12 +437,12 @@ class Exchange: @property def name(self) -> str: """exchange Name (from ccxt)""" - return self._api.name + return self._api.name if not self._is_demo_trading else f"{self._api.name} (Demo)" @property def id(self) -> str: """exchange ccxt id""" - return self._api.id + return self._api.id if not self._is_demo_trading else f"{self._api.id}_demo" @property def timeframes(self) -> list[str]: diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index a9200f926..022c410f0 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -4312,12 +4312,29 @@ def test_fetch_order_or_stoploss_order(default_conf, mocker): @pytest.mark.parametrize("exchange_name", EXCHANGES) -def test_name(default_conf, mocker, exchange_name): - exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name) +def test_name(default_conf_usdt, mocker, exchange_name): + # exchange = get_patched_exchange(mocker, default_conf_usdt, exchange=exchange_name) + api_mock = MagicMock() + api_mock.name = exchange_name.title() + api_mock.id = exchange_name + mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock)) + mocker.patch(f"{EXMS}._load_async_markets") + # mocker.patch(f"{EXMS}.validate_timeframes") + # mocker.patch(f"{EXMS}.validate_stakecurrency") + # mocker.patch(f"{EXMS}.validate_pricing") + default_conf_usdt["exchange"]["name"] = "exchange_name" + exchange = ExchangeResolver.load_exchange(default_conf_usdt, validate=False) assert exchange.name == exchange_name.title() assert exchange.id == exchange_name + default_conf_usdt["exchange"]["demo_trading"] = True + + exchange_demo = ExchangeResolver.load_exchange(default_conf_usdt, validate=False) + + assert exchange_demo.name == f"{exchange_name.title()} (Demo)" + assert exchange_demo.id == f"{exchange_name}_demo" + @pytest.mark.parametrize( "trading_mode,amount", From ff7cddf4cae9864cc68edb479c73ec43f4dda734 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 8 Apr 2026 20:32:05 +0200 Subject: [PATCH 174/315] docs: add documentation for demo bybit mode --- docs/exchanges.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/exchanges.md b/docs/exchanges.md index 14edc11c6..6b966c3a0 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -345,6 +345,15 @@ API Keys for live futures trading must have the following permissions: We do strongly recommend to limit all API keys to the IP you're going to use it from. +### Bybit Demo Mode + +Bybit has a [demo mode](https://learn.bybit.com/en/bybit-guide/how-to-use-bybit-demo-trading) - which can be activated by setting `exchange.demo_trading` to `true` in the configuration. +Bybit uses live markets to simulate your trades (without market impact) - making it work very similar to freqtrade's dry-run mode. + +You'll need to use separate API keys for demo trading, which you can create on bybit's demo page. + +Demo mode is incompatible with dry-run. + ## Bitmart Bitmart requires the API key Memo (the name you give the API key) to go along with the exchange key and secret. From 7c43833a2d6ba9c4b094353edd581d97d13d3202 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 9 Apr 2026 06:39:51 +0200 Subject: [PATCH 175/315] chore: explicitly disable demo mode for binance --- freqtrade/exchange/binance.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 6fab1afe0..36779bcaf 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -46,6 +46,10 @@ class Binance(Exchange): "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], "ws_enabled": True, "has_delisting": True, + # Demo trading + # https://www.binance.com/en/support/faq/detail/9be58f73e5e14338809e3b705b9687dd + # Intentionally Disabled as it's a separate market - not a simulated live market. + "supports_demo_trading": False, } _ft_has_futures: FtHas = { "ohlcv_candle_limit": 499, From e5baa27d51b90a1866e86cd14e7c199a0431a5e5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 9 Apr 2026 06:44:23 +0200 Subject: [PATCH 176/315] fix: dependabot currently doesn't support cooldown for docker ref: https://github.com/dependabot/dependabot-core/issues/14044 --- .github/dependabot.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index bd168014b..c5b85fce7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,8 +1,10 @@ version: 2 updates: - package-ecosystem: docker - cooldown: - default-days: 7 + # Docker does not support cooldowns at the moment. + # https://github.com/dependabot/dependabot-core/issues/14044 + # cooldown: + # default-days: 7 directories: - "/" - "/docker" From 1dbb0f0ee7c63c6c757207fd687ca7a2b09a44af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 04:45:48 +0000 Subject: [PATCH 177/315] chore(deps-dev): bump the types group with 2 updates Bumps the types group with 2 updates: [types-requests](https://github.com/python/typeshed) and [types-python-dateutil](https://github.com/python/typeshed). Updates `types-requests` from 2.33.0.20260327 to 2.33.0.20260402 - [Commits](https://github.com/python/typeshed/commits) Updates `types-python-dateutil` from 2.9.0.20260323 to 2.9.0.20260402 - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-version: 2.33.0.20260402 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types - dependency-name: types-python-dateutil dependency-version: 2.9.0.20260402 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index cd6b0688c..921479828 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -26,9 +26,9 @@ nbconvert==7.17.0 scipy-stubs==1.17.1.3 # keep in sync with `scipy` in `requirements-hyperopt.txt` types-cachetools==6.2.0.20260317 types-filelock==3.2.7 -types-requests==2.33.0.20260327 +types-requests==2.33.0.20260402 types-tabulate==0.10.0.20260308 -types-python-dateutil==2.9.0.20260323 +types-python-dateutil==2.9.0.20260402 pip-audit==2.10.0 # For build step in CI build==1.4.2 From 724ed63c419bff6349e9af121792a182d97a93c6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 9 Apr 2026 06:47:26 +0200 Subject: [PATCH 178/315] chore: bump types in pre-commit config --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 742c5dd93..a32221ee3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,9 +22,9 @@ repos: additional_dependencies: - types-cachetools==6.2.0.20260317 - types-filelock==3.2.7 - - types-requests==2.33.0.20260327 + - types-requests==2.33.0.20260402 - types-tabulate==0.10.0.20260308 - - types-python-dateutil==2.9.0.20260323 + - types-python-dateutil==2.9.0.20260402 - scipy-stubs==1.17.1.3 - SQLAlchemy==2.0.48 # stages: [push] From 4762abd13ff8beec1378d0d16f0f2fb7246b6dd0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 9 Apr 2026 06:54:15 +0200 Subject: [PATCH 179/315] chore: zizmor exception --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c5b85fce7..6e491cd7b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,6 +1,6 @@ version: 2 updates: -- package-ecosystem: docker +- package-ecosystem: docker # zizmor: ignore[dependabot-cooldown] Docker does not support cooldowns at the moment. # Docker does not support cooldowns at the moment. # https://github.com/dependabot/dependabot-core/issues/14044 # cooldown: From 90d538d41af1359d4d2994d08ce5f4ddff3104c4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:08:00 +0000 Subject: [PATCH 180/315] chore(deps): bump fastapi from 0.135.2 to 0.135.3 Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.135.2 to 0.135.3. - [Release notes](https://github.com/fastapi/fastapi/releases) - [Commits](https://github.com/fastapi/fastapi/compare/0.135.2...0.135.3) --- updated-dependencies: - dependency-name: fastapi dependency-version: 0.135.3 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 ac445b27e..89d2573e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,7 @@ orjson==3.11.7 sdnotify==0.3.2 # API Server -fastapi==0.135.2 +fastapi==0.135.3 pydantic==2.12.5 uvicorn==0.42.0 pyjwt==2.12.1 From 55e4250bcd40d45da9c214a580da5068ccecd7a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:08:26 +0000 Subject: [PATCH 181/315] chore(deps): bump requests from 2.33.0 to 2.33.1 Bumps [requests](https://github.com/psf/requests) from 2.33.0 to 2.33.1. - [Release notes](https://github.com/psf/requests/releases) - [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md) - [Commits](https://github.com/psf/requests/compare/v2.33.0...v2.33.1) --- updated-dependencies: - dependency-name: requests dependency-version: 2.33.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- ft_client/requirements.txt | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ft_client/requirements.txt b/ft_client/requirements.txt index 5cabe57a3..3a236a252 100644 --- a/ft_client/requirements.txt +++ b/ft_client/requirements.txt @@ -1,3 +1,3 @@ # Requirements for freqtrade client library -requests==2.33.0 +requests==2.33.1 python-rapidjson==1.23 diff --git a/requirements.txt b/requirements.txt index ac445b27e..17a4eeccf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,7 +16,7 @@ python-telegram-bot==22.7 httpx>=0.24.1 humanize==4.15.0 cachetools==7.0.5 -requests==2.33.0 +requests==2.33.1 urllib3==2.6.3 certifi==2026.2.25 jsonschema==4.26.0 From 3872906a5771726d6251d469c288d4eced7ffa67 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:09:08 +0000 Subject: [PATCH 182/315] chore(deps): bump orjson from 3.11.7 to 3.11.8 Bumps [orjson](https://github.com/ijl/orjson) from 3.11.7 to 3.11.8. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.11.7...3.11.8) --- updated-dependencies: - dependency-name: orjson dependency-version: 3.11.8 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 ac445b27e..037091471 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,7 +31,7 @@ pyarrow==23.0.1; platform_machine != 'armv7l' # Load ticker files 30% faster python-rapidjson==1.23 # Properly format api responses -orjson==3.11.7 +orjson==3.11.8 # Notify systemd sdnotify==0.3.2 From c4aca0c2b99fcf70ae0a0b994a87c1f795873ad1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:11:44 +0000 Subject: [PATCH 183/315] chore(deps-dev): bump mypy from 1.19.1 to 1.20.0 Bumps [mypy](https://github.com/python/mypy) from 1.19.1 to 1.20.0. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.19.1...v1.20.0) --- updated-dependencies: - dependency-name: mypy dependency-version: 1.20.0 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 cd6b0688c..3d4c89130 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt ruff==0.15.8 -mypy==1.19.1 +mypy==1.20.0 pre-commit==4.5.1 pytest==9.0.2 pytest-asyncio==1.3.0 From 968dea5b66cc359b383ad573cf82de147dabc9be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:15:05 +0000 Subject: [PATCH 184/315] chore(deps): bump aiohttp from 3.13.4 to 3.13.5 --- updated-dependencies: - dependency-name: aiohttp dependency-version: 3.13.5 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 ac445b27e..1baa2c312 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ technical==1.5.4 ccxt==4.5.46 cryptography==46.0.6 -aiohttp==3.13.4 +aiohttp==3.13.5 SQLAlchemy==2.0.48 python-telegram-bot==22.7 # can't be hard-pinned due to telegram-bot pinning httpx with ~ From b5ac870eacb0df189b9457d961f61e74855064de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:15:15 +0000 Subject: [PATCH 185/315] chore(deps): bump stable-baselines3 from 2.7.1 to 2.8.0 Bumps [stable-baselines3](https://github.com/DLR-RM/stable-baselines3) from 2.7.1 to 2.8.0. - [Release notes](https://github.com/DLR-RM/stable-baselines3/releases) - [Commits](https://github.com/DLR-RM/stable-baselines3/compare/v2.7.1...v2.8.0) --- updated-dependencies: - dependency-name: stable-baselines3 dependency-version: 2.8.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-freqai-rl.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai-rl.txt b/requirements-freqai-rl.txt index c1a6cc6fa..61d34f386 100644 --- a/requirements-freqai-rl.txt +++ b/requirements-freqai-rl.txt @@ -5,7 +5,7 @@ torch==2.11.0; sys_platform != 'darwin' or platform_machine != 'x86_64' gymnasium==1.2.3 # SB3 >=2.5.0 depends on torch 2.3.0 - which implies it dropped support x86 macos -stable_baselines3==2.7.1; sys_platform != 'darwin' or platform_machine != 'x86_64' +stable_baselines3==2.8.0; sys_platform != 'darwin' or platform_machine != 'x86_64' sb3_contrib>=2.2.1; sys_platform != 'darwin' or platform_machine != 'x86_64' # Progress bar for stable-baselines3 and sb3-contrib tqdm==4.67.3 From 48b178b353d10022cb7aeeb2ec32480b3af288d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 06:16:11 +0000 Subject: [PATCH 186/315] chore(deps): bump pypa/gh-action-pypi-publish from 1.13.0 to 1.14.0 Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.13.0 to 1.14.0. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e...cef221092ed1bacb1cc03d23a2d87d1d172e277b) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-version: 1.14.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index adbd20aaf..16846d1ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -388,7 +388,7 @@ jobs: merge-multiple: true - name: Publish to PyPI (Test) - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 with: repository-url: https://test.pypi.org/legacy/ @@ -417,7 +417,7 @@ jobs: merge-multiple: true - name: Publish to PyPI - uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0 + uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0 docker-build: From d93ae4e92d0e4154366c00e46b1a014661fab296 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 11:45:21 +0000 Subject: [PATCH 187/315] chore(deps): bump ccxt from 4.5.46 to 4.5.47 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.5.46 to 4.5.47. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Commits](https://github.com/ccxt/ccxt/compare/v4.5.46...v4.5.47) --- updated-dependencies: - dependency-name: ccxt dependency-version: 4.5.47 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 51ca4bdab..ec800cd7e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ ft-pandas-ta==0.3.16 ta-lib==0.6.8 technical==1.5.4 -ccxt==4.5.46 +ccxt==4.5.47 cryptography==46.0.6 aiohttp==3.13.5 SQLAlchemy==2.0.48 From 5dd2ad532b135963868f5eb4ef068026f5e6346d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 22:39:24 +0000 Subject: [PATCH 188/315] chore(deps): bump cryptography from 46.0.6 to 46.0.7 Bumps [cryptography](https://github.com/pyca/cryptography) from 46.0.6 to 46.0.7. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/46.0.6...46.0.7) --- updated-dependencies: - dependency-name: cryptography dependency-version: 46.0.7 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ec800cd7e..f33fd73c2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ ta-lib==0.6.8 technical==1.5.4 ccxt==4.5.47 -cryptography==46.0.6 +cryptography==46.0.7 aiohttp==3.13.5 SQLAlchemy==2.0.48 python-telegram-bot==22.7 From 85ce0a18408cc1191abb9040aad86576b93fb010 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 9 Apr 2026 19:21:21 +0200 Subject: [PATCH 189/315] chore: allow cryptography security upgrades --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index fd23460bb..b62878fad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -222,6 +222,7 @@ exclude-newer = "1 week" [tool.uv.exclude-newer-package] ccxt = false +cryptography = "1 days" [tool.ruff] line-length = 100 From e5a8aae83120a2e6d58ce87844eb0c8068dd3468 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Apr 2026 09:05:41 +0200 Subject: [PATCH 190/315] docs: Update hyperliquid vault / subaccount documentation --- docs/exchanges.md | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 6b966c3a0..7f1fee3f6 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -438,31 +438,50 @@ Hyperliquid handles deposits and withdrawals on the Arbitrum One chain, a Layer * Create a different software wallet, only transfer the funds you want to trade with to that wallet, and use that wallet to trade on Hyperliquid. * If you have funds you don't want to use for trading (after making a profit for example), transfer them back to your hardware wallet. -### Hyperliquid Vault / Subaccount -Hyperliquid allows you to create either a vault or a subaccount. -To use these with Freqtrade, you will need to use the following configuration pattern: +!!! Warning "Vaults and Subaccounts" + You can only use either a vault or a subaccount - not both at the same time. + +### Hyperliquid Subaccount + +Hyperliquid allows you to create subaccounts with sufficient previous trading volume. +To use subaccounts with Freqtrade, you will need to use the following configuration pattern: ``` json "exchange": { "name": "hyperliquid", - "walletAddress": "your_master_wallet_address", // Your master wallet address (not the API wallet address and not the vault/subaccount address). + "walletAddress": "your_master_wallet_address", // Your master wallet address (not the API wallet or vault address - but not subaccount address). "privateKey": "your_api_private_key", // API wallet private key (see https://app.hyperliquid.xyz/API). You'll only need the private key. "ccxt_config": { "options": { - "vaultAddress": "your_vault_address", // Optional, only if you want to use a vault ... - "subAccountAddress": "your_subaccount_address" // OR optional, only if you want to use a subaccount + "subAccountAddress": "your_subaccount_address" // Required if you want to use a subaccount. } }, // ... } ``` -Your balance and trades will now be used from your vault / subaccount - and no longer from your main account. +Your balance and trades will now be used from your subaccount - and no longer from your main account. -!!! Note - You can only use either a vault or a subaccount - not both at the same time. +### Hyperliquid Vault +Hyperliquid allows you to create vaults. To use vaults with Freqtrade, you will need to use the following configuration pattern: + +``` json +"exchange": { + "name": "hyperliquid", + "walletAddress": "your_vault_address", // Your vault wallet address (Must also be added below in the ccxt_config.options.vaultAddress field) + "privateKey": "your_api_private_key", // API wallet private key (see https://app.hyperliquid.xyz/API). You'll only need the private key. + "ccxt_config": { + "options": { + "vaultAddress": "your_vault_address", // Optional, only if you want to use a vault ... (vault address must also be added to walletAdress) + } + }, + // ... +} +``` + +Your balance and trades will now be used from your vault - and no longer from your main account. ### Historic Hyperliquid data From e8501dfb7a5e91453418a1fb643b277cc15ad92a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Apr 2026 13:09:13 +0200 Subject: [PATCH 191/315] chore: fix double-space typos --- build_helpers/create_command_partials.py | 2 +- freqtrade/exchange/check_exchange.py | 6 ++---- freqtrade/freqai/data_kitchen.py | 2 +- .../prediction_models/SKLearnRandomForestClassifier.py | 2 +- freqtrade/freqai/prediction_models/XGBoostRFClassifier.py | 2 +- freqtrade/optimize/backtesting.py | 2 +- freqtrade/optimize/optimize_reports/optimize_reports.py | 2 +- freqtrade/persistence/wallet_history.py | 2 +- freqtrade/plot/plotting.py | 4 ++-- freqtrade/rpc/rpc.py | 2 +- freqtrade/rpc/telegram.py | 2 +- 11 files changed, 13 insertions(+), 15 deletions(-) diff --git a/build_helpers/create_command_partials.py b/build_helpers/create_command_partials.py index 3f5bad52e..fe7212993 100644 --- a/build_helpers/create_command_partials.py +++ b/build_helpers/create_command_partials.py @@ -87,7 +87,7 @@ def extract_command_partials(): help_output = _get_help_output(subparser) _write_partial_file(f"docs/commands/{command}.md", help_output) else: - print(f" Warning: subcommand '{command}' not found in parser") + print(f" Warning: subcommand '{command}' not found in parser") # freqtrade-client still uses subprocess as requested print("Running for freqtrade-client") diff --git a/freqtrade/exchange/check_exchange.py b/freqtrade/exchange/check_exchange.py index 583868744..30a416cdd 100644 --- a/freqtrade/exchange/check_exchange.py +++ b/freqtrade/exchange/check_exchange.py @@ -51,12 +51,10 @@ def check_exchange(config: Config, check_for_bad: bool = True) -> bool: if not valid: if check_for_bad: raise OperationalException( - f'Exchange "{exchange}" will not work with Freqtrade. Reason: {reason}.' + f'Exchange "{exchange}" will not work with Freqtrade. Reason: {reason}.' ) else: - logger.warning( - f'Exchange "{exchange}" will not work with Freqtrade. Reason: {reason}.' - ) + logger.warning(f'Exchange "{exchange}" will not work with Freqtrade. Reason: {reason}.') if MAP_EXCHANGE_CHILDCLASS.get(exchange, exchange) in SUPPORTED_EXCHANGES: logger.info( diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 30826b174..df7c827f9 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -990,7 +990,7 @@ class FreqaiDataKitchen: are populated. The main example use is when predicting maxima and minima, the argrelextrema - function cannot know the maxima/minima at the edges of the timerange. To improve + function cannot know the maxima/minima at the edges of the timerange. To improve model accuracy, it is best to compute argrelextrema on the full timerange and then use this function to cut off the edges (buffer) by the kernel. diff --git a/freqtrade/freqai/prediction_models/SKLearnRandomForestClassifier.py b/freqtrade/freqai/prediction_models/SKLearnRandomForestClassifier.py index b008a8ecd..9d32b148b 100644 --- a/freqtrade/freqai/prediction_models/SKLearnRandomForestClassifier.py +++ b/freqtrade/freqai/prediction_models/SKLearnRandomForestClassifier.py @@ -63,7 +63,7 @@ class SKLearnRandomForestClassifier(BaseClassifierModel): ) -> tuple[DataFrame, npt.NDArray[np.int_]]: """ Filter the prediction features data and predict with it. - :param unfiltered_df: Full dataframe for the current backtest period. + :param unfiltered_df: Full dataframe for the current backtest period. :return: :pred_df: dataframe containing the predictions :do_predict: np.array of 1s and 0s to indicate places where freqai needed to remove diff --git a/freqtrade/freqai/prediction_models/XGBoostRFClassifier.py b/freqtrade/freqai/prediction_models/XGBoostRFClassifier.py index 6760ad285..1028eebbb 100644 --- a/freqtrade/freqai/prediction_models/XGBoostRFClassifier.py +++ b/freqtrade/freqai/prediction_models/XGBoostRFClassifier.py @@ -67,7 +67,7 @@ class XGBoostRFClassifier(BaseClassifierModel): ) -> tuple[DataFrame, npt.NDArray[np.int_]]: """ Filter the prediction features data and predict with it. - :param unfiltered_df: Full dataframe for the current backtest period. + :param unfiltered_df: Full dataframe for the current backtest period. :return: :pred_df: dataframe containing the predictions :do_predict: np.array of 1s and 0s to indicate places where freqai needed to remove diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index e9edb2569..f4f5e9d62 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -757,7 +757,7 @@ class Backtesting: ) -> bool: """ Check if an order is open and if it should've filled. - :return: True if the order filled. + :return: True if the order filled. """ if order and self._get_order_filled(order.ft_price, row): order.close_bt_order(current_date, trade) diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index bea047202..ba84d8834 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -197,7 +197,7 @@ def generate_pair_metrics( # skip_nan: bool = False, ) -> list[dict]: """ - Generates and returns a list for the given backtest data and the results dataframe + Generates and returns a list for the given backtest data and the results dataframe :param pairlist: Pairlist used :param stake_currency: stake-currency - used to correctly name headers :param starting_balance: Starting balance diff --git a/freqtrade/persistence/wallet_history.py b/freqtrade/persistence/wallet_history.py index 2aef7000d..08e180937 100644 --- a/freqtrade/persistence/wallet_history.py +++ b/freqtrade/persistence/wallet_history.py @@ -45,6 +45,6 @@ class WalletHistory(ModelBase): def __repr__(self) -> str: return ( f"WalletHistory(timestamp={self.timestamp}, currency={self.currency}, " - f"rate={self.rate}, total_quote={self.total_quote}, " + f"rate={self.rate}, total_quote={self.total_quote}, " f"balance={self.balance}, leverage={self.leverage})" ) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 6c6f32ea1..085a198ca 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -356,7 +356,7 @@ def plot_area( :param indicator_b: indicator name as populated in strategy :param label: label for the filled area :param fill_color: color to be used for the filled area - :return: fig with added filled_traces plot + :return: fig with added filled_traces plot """ if indicator_a in data and indicator_b in data: # make lines invisible to get the area plotted, only. @@ -383,7 +383,7 @@ def add_areas(fig, row: int, data: pd.DataFrame, indicators) -> make_subplots: :param data: candlestick DataFrame :param indicators: dict with indicators. ie.: plot_config['main_plot'] or plot_config['subplots'][subplot_label] - :return: fig with added filled_traces plot + :return: fig with added filled_traces plot """ for indicator, ind_conf in indicators.items(): if "fill_to" in ind_conf: diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 446a474ef..63f00a22d 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -1407,7 +1407,7 @@ class RPC: } def _rpc_locks(self) -> dict[str, Any]: - """Returns the current locks""" + """Returns the current locks""" locks = PairLocks.get_pair_locks(None) return {"lock_count": len(locks), "locks": [lock.to_json() for lock in locks]} diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 3d7cf1fb3..8e88cc4e7 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -2243,7 +2243,7 @@ class Telegram(RPCHandler): else: raise RPCException( "Invalid usage of command /marketdir. \n" - "Usage: */marketdir [short | long | even | none]*" + "Usage: */marketdir [short | long | even | none]*" ) async def _tg_info(self, update: Update, context: CallbackContext) -> None: From f838db64096c0b347a4a5e7670bcb2a7bc93bbd1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Apr 2026 13:38:19 +0200 Subject: [PATCH 192/315] fix: show balance also for old backtests --- .../optimize/optimize_reports/bt_output.py | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index 754ed6b9f..23963038c 100644 --- a/freqtrade/optimize/optimize_reports/bt_output.py +++ b/freqtrade/optimize/optimize_reports/bt_output.py @@ -287,24 +287,27 @@ def text_table_add_metrics(strat_results: dict) -> None: if "trading_mode" in strat_results else [] ) - wallet_metrics: list[tuple[str, str]] = [] + wallet_metrics: list[tuple[str, str]] = [ + ( + "Min/Max balance realized", + f"{fmt_coin(strat_results['csum_min'], stake)} / " + f"{fmt_coin(strat_results['csum_max'], stake)}", + ), + ] if wallet_stats := strat_results.get("wallet_stats"): - wallet_metrics = [ - ( - "Min/Max balance realized", - f"{fmt_coin(strat_results['csum_min'], stake)} / " - f"{fmt_coin(strat_results['csum_max'], stake)}", - ), - ( - "Min/Max balance unrealized", - f"{fmt_coin(wallet_stats['low_balance'], stake)} / " - f"{fmt_coin(wallet_stats['high_balance'], stake)}", - ), - ( - "Min/Max balance dates", - f"{wallet_stats['low_date']} / {wallet_stats['high_date']}", - ), - ] + wallet_metrics.extend( + [ + ( + "Min/Max balance unrealized", + f"{fmt_coin(wallet_stats['low_balance'], stake)} / " + f"{fmt_coin(wallet_stats['high_balance'], stake)}", + ), + ( + "Min/Max balance dates", + f"{wallet_stats['low_date']} / {wallet_stats['high_date']}", + ), + ] + ) # 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 From 68d514db9157e56ff19e619ed6d056ed77847e12 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Apr 2026 16:41:07 +0200 Subject: [PATCH 193/315] test: split test_btanalysis and test_metrics --- tests/data/test_btanalysis.py | 390 +-------------------------------- tests/data/test_metrics.py | 395 ++++++++++++++++++++++++++++++++++ 2 files changed, 398 insertions(+), 387 deletions(-) create mode 100644 tests/data/test_metrics.py diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py index 82ff56c3a..e6ca0bd8a 100644 --- a/tests/data/test_btanalysis.py +++ b/tests/data/test_btanalysis.py @@ -1,10 +1,10 @@ -from datetime import UTC, datetime, timedelta +from datetime import UTC, datetime from pathlib import Path from unittest.mock import MagicMock from zipfile import ZipFile import pytest -from pandas import DataFrame, DateOffset, Timestamp, to_datetime +from pandas import DataFrame, to_datetime from freqtrade.configuration import TimeRange from freqtrade.constants import LAST_BT_RESULT_FN @@ -21,22 +21,7 @@ from freqtrade.data.btanalysis import ( load_trades, load_trades_from_db, ) -from freqtrade.data.history import load_data, load_pair_history -from freqtrade.data.metrics import ( - calculate_cagr, - calculate_calmar, - calculate_csum, - calculate_expectancy, - calculate_market_change, - calculate_max_drawdown, - calculate_sharpe, - calculate_sortino, - calculate_sqn, - calculate_underwater, - combine_dataframes_with_mean, - combined_dataframes_with_rel_mean, - create_cum_profit, -) +from freqtrade.data.history import load_pair_history from freqtrade.exceptions import OperationalException from freqtrade.util import dt_utc from tests.conftest import CURRENT_TEST_STRATEGY, create_mock_trades @@ -253,375 +238,6 @@ def test_load_trades(default_conf, mocker): assert bt_mock.call_count == 0 -def test_calculate_market_change(testdatadir): - pairs = ["ETH/BTC", "ADA/BTC"] - data = load_data(datadir=testdatadir, pairs=pairs, timeframe="5m") - result = calculate_market_change(data) - assert isinstance(result, float) - assert pytest.approx(result) == 0.01100002 - - result = calculate_market_change(data, min_date=dt_utc(2018, 1, 20)) - assert isinstance(result, float) - assert pytest.approx(result) == 0.0375149 - - # Move min-date after the last date - result = calculate_market_change(data, min_date=dt_utc(2018, 2, 20)) - assert pytest.approx(result) == 0.0 - - -def test_combine_dataframes_with_mean(testdatadir): - pairs = ["ETH/BTC", "ADA/BTC"] - data = load_data(datadir=testdatadir, pairs=pairs, timeframe="5m") - df = combine_dataframes_with_mean(data) - assert isinstance(df, DataFrame) - assert "ETH/BTC" in df.columns - assert "ADA/BTC" in df.columns - assert "mean" in df.columns - - -def test_combined_dataframes_with_rel_mean(testdatadir): - pairs = ["BTC/USDT", "XRP/USDT"] - data = load_data(datadir=testdatadir, pairs=pairs, timeframe="5m") - df = combined_dataframes_with_rel_mean( - data, - fromdt=data["BTC/USDT"].at[0, "date"], - todt=data["BTC/USDT"].at[data["BTC/USDT"].index[-1], "date"], - ) - assert isinstance(df, DataFrame) - assert "BTC/USDT" not in df.columns - assert "XRP/USDT" not in df.columns - assert "mean" in df.columns - assert "rel_mean" in df.columns - assert "count" in df.columns - assert df.iloc[0]["count"] == 2 - assert df.iloc[-1]["count"] == 2 - assert len(df) < len(data["BTC/USDT"]) - assert df["rel_mean"].between(-0.5, 0.5).all() - - -def test_combine_dataframes_with_mean_no_data(testdatadir): - pairs = ["ETH/BTC", "ADA/BTC"] - data = load_data(datadir=testdatadir, pairs=pairs, timeframe="6m") - with pytest.raises(ValueError, match=r"No data provided\."): - combine_dataframes_with_mean(data) - - -def test_create_cum_profit(testdatadir): - filename = testdatadir / "backtest_results/backtest-result.json" - bt_data = load_backtest_data(filename) - timerange = TimeRange.parse_timerange("20180110-20180112") - - df = load_pair_history(pair="TRX/BTC", timeframe="5m", datadir=testdatadir, timerange=timerange) - - cum_profits = create_cum_profit( - df.set_index("date"), bt_data[bt_data["pair"] == "TRX/BTC"], "cum_profits", timeframe="5m" - ) - assert "cum_profits" in cum_profits.columns - assert cum_profits.iloc[0]["cum_profits"] == 0 - assert pytest.approx(cum_profits.iloc[-1]["cum_profits"]) == 9.0225563e-05 - - -def test_create_cum_profit1(testdatadir): - filename = testdatadir / "backtest_results/backtest-result.json" - bt_data = load_backtest_data(filename) - # Move close-time to "off" the candle, to make sure the logic still works - bt_data["close_date"] = bt_data.loc[:, "close_date"] + DateOffset(seconds=20) - timerange = TimeRange.parse_timerange("20180110-20180112") - - df = load_pair_history(pair="TRX/BTC", timeframe="5m", datadir=testdatadir, timerange=timerange) - - cum_profits = create_cum_profit( - df.set_index("date"), bt_data[bt_data["pair"] == "TRX/BTC"], "cum_profits", timeframe="5m" - ) - assert "cum_profits" in cum_profits.columns - assert cum_profits.iloc[0]["cum_profits"] == 0 - assert pytest.approx(cum_profits.iloc[-1]["cum_profits"]) == 9.0225563e-05 - - with pytest.raises(ValueError, match=r"Trade dataframe empty\."): - create_cum_profit( - df.set_index("date"), - bt_data[bt_data["pair"] == "NOTAPAIR"], - "cum_profits", - timeframe="5m", - ) - - -def test_calculate_max_drawdown(testdatadir): - filename = testdatadir / "backtest_results/backtest-result.json" - bt_data = load_backtest_data(filename) - drawdown = calculate_max_drawdown(bt_data, value_col="profit_abs") - assert isinstance(drawdown.relative_account_drawdown, float) - assert pytest.approx(drawdown.relative_account_drawdown) == 0.29753914 - assert isinstance(drawdown.high_date, Timestamp) - assert isinstance(drawdown.low_date, Timestamp) - assert isinstance(drawdown.high_value, float) - assert isinstance(drawdown.low_value, float) - assert drawdown.high_date == Timestamp("2018-01-16 19:30:00", tz="UTC") - assert drawdown.low_date == Timestamp("2018-01-16 22:25:00", tz="UTC") - - underwater = calculate_underwater(bt_data) - assert isinstance(underwater, DataFrame) - - with pytest.raises(ValueError, match=r"Trade dataframe empty\."): - calculate_max_drawdown(DataFrame()) - - with pytest.raises(ValueError, match=r"Trade dataframe empty\."): - calculate_underwater(DataFrame()) - - -def test_calculate_csum(testdatadir): - filename = testdatadir / "backtest_results/backtest-result.json" - bt_data = load_backtest_data(filename) - csum_min, csum_max = calculate_csum(bt_data) - - assert isinstance(csum_min, float) - assert isinstance(csum_max, float) - assert csum_min < csum_max - assert csum_min < 0.0001 - assert csum_max > 0.0002 - csum_min1, csum_max1 = calculate_csum(bt_data, 5) - - assert csum_min1 == csum_min + 5 - assert csum_max1 == csum_max + 5 - - with pytest.raises(ValueError, match=r"Trade dataframe empty\."): - csum_min, csum_max = calculate_csum(DataFrame()) - - -def test_calculate_expectancy(testdatadir): - filename = testdatadir / "backtest_results/backtest-result.json" - bt_data = load_backtest_data(filename) - - expectancy, expectancy_ratio = calculate_expectancy(DataFrame()) - assert expectancy == 0.0 - assert expectancy_ratio == 100 - - expectancy, expectancy_ratio = calculate_expectancy(bt_data) - assert isinstance(expectancy, float) - assert isinstance(expectancy_ratio, float) - assert pytest.approx(expectancy) == 5.820687070932315e-06 - assert pytest.approx(expectancy_ratio) == 0.07151374226574791 - - data = {"profit_abs": [100, 200, 50, -150, 300, -100, 80, -30]} - df = DataFrame(data) - expectancy, expectancy_ratio = calculate_expectancy(df) - - assert pytest.approx(expectancy) == 56.25 - assert pytest.approx(expectancy_ratio) == 0.60267857 - - -def test_calculate_sortino(testdatadir): - filename = testdatadir / "backtest_results/backtest-result.json" - bt_data = load_backtest_data(filename) - - sortino = calculate_sortino(DataFrame(), None, None, 0) - assert sortino == 0.0 - - sortino = calculate_sortino( - bt_data, - bt_data["open_date"].min(), - bt_data["close_date"].max(), - 0.01, - ) - assert isinstance(sortino, float) - assert pytest.approx(sortino) == 35.17722 - - -def test_calculate_sharpe(testdatadir): - filename = testdatadir / "backtest_results/backtest-result.json" - bt_data = load_backtest_data(filename) - - sharpe = calculate_sharpe(DataFrame(), None, None, 0) - assert sharpe == 0.0 - - sharpe = calculate_sharpe( - bt_data, - bt_data["open_date"].min(), - bt_data["close_date"].max(), - 0.01, - ) - assert isinstance(sharpe, float) - assert pytest.approx(sharpe) == 44.5078669 - - -def test_calculate_calmar(testdatadir): - filename = testdatadir / "backtest_results/backtest-result.json" - bt_data = load_backtest_data(filename) - - calmar = calculate_calmar(DataFrame(), None, None, 0) - assert calmar == 0.0 - - calmar = calculate_calmar( - bt_data, - bt_data["open_date"].min(), - bt_data["close_date"].max(), - 0.01, - ) - assert isinstance(calmar, float) - assert pytest.approx(calmar) == 559.040508 - - -def test_calculate_sqn(testdatadir): - filename = testdatadir / "backtest_results/backtest-result.json" - bt_data = load_backtest_data(filename) - - sqn = calculate_sqn(DataFrame(), 0) - assert sqn == 0.0 - - sqn = calculate_sqn( - bt_data, - 0.01, - ) - assert isinstance(sqn, float) - assert pytest.approx(sqn) == 3.2991 - - -@pytest.mark.parametrize( - "profits,starting_balance,expected_sqn,description", - [ - ([1.0, -0.5, 2.0, -1.0, 0.5, 1.5, -0.5, 1.0], 100, 1.3229, "Mixed profits/losses"), - ([], 100, 0.0, "Empty dataframe"), - ([1.0, 0.5, 2.0, 1.5, 0.8], 100, 4.3657, "All winning trades"), - ([-1.0, -0.5, -2.0, -1.5, -0.8], 100, -4.3657, "All losing trades"), - ([1.0], 100, -100, "Single trade"), - ], -) -def test_calculate_sqn_cases(profits, starting_balance, expected_sqn, description): - """ - Test SQN calculation with various scenarios: - """ - trades = DataFrame({"profit_abs": profits}) - sqn = calculate_sqn(trades, starting_balance=starting_balance) - - assert isinstance(sqn, float) - assert pytest.approx(sqn, rel=1e-4) == expected_sqn - - -@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 - (1000, 1010, 0, 0.0), # zero days - (-100, 100, 365, 0.0), # negative starting balance - ], -) -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, - -0.025782, - 0.010400, - 0.012374, - 0.012467, - 0.114741, - 0.010303, - 0.010088, - -0.033961, - 0.010680, - 0.010886, - -0.029274, - 0.011178, - 0.010693, - 0.010711, - ] - - dates = [dt_utc(2020, 1, 1) + timedelta(days=i) for i in range(len(values))] - df = DataFrame(zip(values, dates, strict=False), columns=["profit", "open_date"]) - # sort by profit and reset index - df = df.sort_values("profit").reset_index(drop=True) - df1 = df.copy() - drawdown = calculate_max_drawdown( - df, date_col="open_date", starting_balance=0.2, value_col="profit" - ) - # Ensure df has not been altered. - assert df.equals(df1) - - assert isinstance(drawdown.drawdown_abs, float) - assert isinstance(drawdown.relative_account_drawdown, float) - # High must be before low - assert drawdown.high_date < drawdown.low_date - # High value must be higher than low value - assert drawdown.high_value > drawdown.low_value - assert drawdown.drawdown_abs == 0.091755 - assert pytest.approx(drawdown.relative_account_drawdown) == 0.32129575 - - df = DataFrame(zip(values[:5], dates[:5], strict=False), columns=["profit", "open_date"]) - # No losing trade ... - drawdown = calculate_max_drawdown(df, date_col="open_date", value_col="profit") - assert drawdown.drawdown_abs == 0.0 - assert drawdown.low_value == 0.0 - assert drawdown.current_high_value >= 0.0 - assert drawdown.current_drawdown_abs == 0.0 - - df1 = DataFrame(zip(values[:5], dates[:5], strict=False), columns=["profit", "open_date"]) - df1.loc[:, "profit"] = df1["profit"] * -1 - # No winning trade ... - drawdown = calculate_max_drawdown(df1, date_col="open_date", value_col="profit") - assert drawdown.drawdown_abs == 0.055545 - assert drawdown.high_value == 0.0 - assert drawdown.current_high_value == 0.0 - assert drawdown.current_drawdown_abs == 0.055545 - - -@pytest.mark.parametrize( - "profits,relative,highd,lowdays,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, 500.0, 0.5), - ], -) -def test_calculate_max_drawdown_abs(profits, relative, highd, lowdays, 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 - """ - init_date = datetime(2020, 1, 1, tzinfo=UTC) - dates = [init_date + timedelta(days=i) for i in range(len(profits))] - df = DataFrame(zip(profits, dates, strict=False), 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 = 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.drawdown_abs, float) - assert isinstance(drawdown.relative_account_drawdown, float) - assert drawdown.high_date == init_date + timedelta(days=highd) - assert drawdown.low_date == init_date + timedelta(days=lowdays) - - # High must be before low - assert drawdown.high_date < drawdown.low_date - # High value must be higher than low value - assert drawdown.high_value > drawdown.low_value - assert drawdown.drawdown_abs == result - assert pytest.approx(drawdown.relative_account_drawdown) == result_rel - - def test_load_file_from_zip(tmp_path): with pytest.raises(ValueError, match=r"Zip file .* not found\."): load_file_from_zip(tmp_path / "test.zip", "testfile.txt") diff --git a/tests/data/test_metrics.py b/tests/data/test_metrics.py new file mode 100644 index 000000000..d19d9e328 --- /dev/null +++ b/tests/data/test_metrics.py @@ -0,0 +1,395 @@ +from datetime import UTC, datetime, timedelta + +import pytest +from pandas import DataFrame, DateOffset, Timestamp + +from freqtrade.configuration import TimeRange +from freqtrade.data.btanalysis import ( + load_backtest_data, +) +from freqtrade.data.history import load_data, load_pair_history +from freqtrade.data.metrics import ( + calculate_cagr, + calculate_calmar, + calculate_csum, + calculate_expectancy, + calculate_market_change, + calculate_max_drawdown, + calculate_sharpe, + calculate_sortino, + calculate_sqn, + calculate_underwater, + combine_dataframes_with_mean, + combined_dataframes_with_rel_mean, + create_cum_profit, +) +from freqtrade.util import dt_utc + + +def test_calculate_market_change(testdatadir): + pairs = ["ETH/BTC", "ADA/BTC"] + data = load_data(datadir=testdatadir, pairs=pairs, timeframe="5m") + result = calculate_market_change(data) + assert isinstance(result, float) + assert pytest.approx(result) == 0.01100002 + + result = calculate_market_change(data, min_date=dt_utc(2018, 1, 20)) + assert isinstance(result, float) + assert pytest.approx(result) == 0.0375149 + + # Move min-date after the last date + result = calculate_market_change(data, min_date=dt_utc(2018, 2, 20)) + assert pytest.approx(result) == 0.0 + + +def test_combine_dataframes_with_mean(testdatadir): + pairs = ["ETH/BTC", "ADA/BTC"] + data = load_data(datadir=testdatadir, pairs=pairs, timeframe="5m") + df = combine_dataframes_with_mean(data) + assert isinstance(df, DataFrame) + assert "ETH/BTC" in df.columns + assert "ADA/BTC" in df.columns + assert "mean" in df.columns + + +def test_combined_dataframes_with_rel_mean(testdatadir): + pairs = ["BTC/USDT", "XRP/USDT"] + data = load_data(datadir=testdatadir, pairs=pairs, timeframe="5m") + df = combined_dataframes_with_rel_mean( + data, + fromdt=data["BTC/USDT"].at[0, "date"], + todt=data["BTC/USDT"].at[data["BTC/USDT"].index[-1], "date"], + ) + assert isinstance(df, DataFrame) + assert "BTC/USDT" not in df.columns + assert "XRP/USDT" not in df.columns + assert "mean" in df.columns + assert "rel_mean" in df.columns + assert "count" in df.columns + assert df.iloc[0]["count"] == 2 + assert df.iloc[-1]["count"] == 2 + assert len(df) < len(data["BTC/USDT"]) + assert df["rel_mean"].between(-0.5, 0.5).all() + + +def test_combine_dataframes_with_mean_no_data(testdatadir): + pairs = ["ETH/BTC", "ADA/BTC"] + data = load_data(datadir=testdatadir, pairs=pairs, timeframe="6m") + with pytest.raises(ValueError, match=r"No data provided\."): + combine_dataframes_with_mean(data) + + +def test_create_cum_profit(testdatadir): + filename = testdatadir / "backtest_results/backtest-result.json" + bt_data = load_backtest_data(filename) + timerange = TimeRange.parse_timerange("20180110-20180112") + + df = load_pair_history(pair="TRX/BTC", timeframe="5m", datadir=testdatadir, timerange=timerange) + + cum_profits = create_cum_profit( + df.set_index("date"), bt_data[bt_data["pair"] == "TRX/BTC"], "cum_profits", timeframe="5m" + ) + assert "cum_profits" in cum_profits.columns + assert cum_profits.iloc[0]["cum_profits"] == 0 + assert pytest.approx(cum_profits.iloc[-1]["cum_profits"]) == 9.0225563e-05 + + +def test_create_cum_profit1(testdatadir): + filename = testdatadir / "backtest_results/backtest-result.json" + bt_data = load_backtest_data(filename) + # Move close-time to "off" the candle, to make sure the logic still works + bt_data["close_date"] = bt_data.loc[:, "close_date"] + DateOffset(seconds=20) + timerange = TimeRange.parse_timerange("20180110-20180112") + + df = load_pair_history(pair="TRX/BTC", timeframe="5m", datadir=testdatadir, timerange=timerange) + + cum_profits = create_cum_profit( + df.set_index("date"), bt_data[bt_data["pair"] == "TRX/BTC"], "cum_profits", timeframe="5m" + ) + assert "cum_profits" in cum_profits.columns + assert cum_profits.iloc[0]["cum_profits"] == 0 + assert pytest.approx(cum_profits.iloc[-1]["cum_profits"]) == 9.0225563e-05 + + with pytest.raises(ValueError, match=r"Trade dataframe empty\."): + create_cum_profit( + df.set_index("date"), + bt_data[bt_data["pair"] == "NOTAPAIR"], + "cum_profits", + timeframe="5m", + ) + + +def test_calculate_max_drawdown(testdatadir): + filename = testdatadir / "backtest_results/backtest-result.json" + bt_data = load_backtest_data(filename) + drawdown = calculate_max_drawdown(bt_data, value_col="profit_abs") + assert isinstance(drawdown.relative_account_drawdown, float) + assert pytest.approx(drawdown.relative_account_drawdown) == 0.29753914 + assert isinstance(drawdown.high_date, Timestamp) + assert isinstance(drawdown.low_date, Timestamp) + assert isinstance(drawdown.high_value, float) + assert isinstance(drawdown.low_value, float) + assert drawdown.high_date == Timestamp("2018-01-16 19:30:00", tz="UTC") + assert drawdown.low_date == Timestamp("2018-01-16 22:25:00", tz="UTC") + + underwater = calculate_underwater(bt_data) + assert isinstance(underwater, DataFrame) + + with pytest.raises(ValueError, match=r"Trade dataframe empty\."): + calculate_max_drawdown(DataFrame()) + + with pytest.raises(ValueError, match=r"Trade dataframe empty\."): + calculate_underwater(DataFrame()) + + +def test_calculate_csum(testdatadir): + filename = testdatadir / "backtest_results/backtest-result.json" + bt_data = load_backtest_data(filename) + csum_min, csum_max = calculate_csum(bt_data) + + assert isinstance(csum_min, float) + assert isinstance(csum_max, float) + assert csum_min < csum_max + assert csum_min < 0.0001 + assert csum_max > 0.0002 + csum_min1, csum_max1 = calculate_csum(bt_data, 5) + + assert csum_min1 == csum_min + 5 + assert csum_max1 == csum_max + 5 + + with pytest.raises(ValueError, match=r"Trade dataframe empty\."): + csum_min, csum_max = calculate_csum(DataFrame()) + + +def test_calculate_expectancy(testdatadir): + filename = testdatadir / "backtest_results/backtest-result.json" + bt_data = load_backtest_data(filename) + + expectancy, expectancy_ratio = calculate_expectancy(DataFrame()) + assert expectancy == 0.0 + assert expectancy_ratio == 100 + + expectancy, expectancy_ratio = calculate_expectancy(bt_data) + assert isinstance(expectancy, float) + assert isinstance(expectancy_ratio, float) + assert pytest.approx(expectancy) == 5.820687070932315e-06 + assert pytest.approx(expectancy_ratio) == 0.07151374226574791 + + data = {"profit_abs": [100, 200, 50, -150, 300, -100, 80, -30]} + df = DataFrame(data) + expectancy, expectancy_ratio = calculate_expectancy(df) + + assert pytest.approx(expectancy) == 56.25 + assert pytest.approx(expectancy_ratio) == 0.60267857 + + +def test_calculate_sortino(testdatadir): + filename = testdatadir / "backtest_results/backtest-result.json" + bt_data = load_backtest_data(filename) + + sortino = calculate_sortino(DataFrame(), None, None, 0) + assert sortino == 0.0 + + sortino = calculate_sortino( + bt_data, + bt_data["open_date"].min(), + bt_data["close_date"].max(), + 0.01, + ) + assert isinstance(sortino, float) + assert pytest.approx(sortino) == 35.17722 + + +def test_calculate_sharpe(testdatadir): + filename = testdatadir / "backtest_results/backtest-result.json" + bt_data = load_backtest_data(filename) + + sharpe = calculate_sharpe(DataFrame(), None, None, 0) + assert sharpe == 0.0 + + sharpe = calculate_sharpe( + bt_data, + bt_data["open_date"].min(), + bt_data["close_date"].max(), + 0.01, + ) + assert isinstance(sharpe, float) + assert pytest.approx(sharpe) == 44.5078669 + + +def test_calculate_calmar(testdatadir): + filename = testdatadir / "backtest_results/backtest-result.json" + bt_data = load_backtest_data(filename) + + calmar = calculate_calmar(DataFrame(), None, None, 0) + assert calmar == 0.0 + + calmar = calculate_calmar( + bt_data, + bt_data["open_date"].min(), + bt_data["close_date"].max(), + 0.01, + ) + assert isinstance(calmar, float) + assert pytest.approx(calmar) == 559.040508 + + +def test_calculate_sqn(testdatadir): + filename = testdatadir / "backtest_results/backtest-result.json" + bt_data = load_backtest_data(filename) + + sqn = calculate_sqn(DataFrame(), 0) + assert sqn == 0.0 + + sqn = calculate_sqn( + bt_data, + 0.01, + ) + assert isinstance(sqn, float) + assert pytest.approx(sqn) == 3.2991 + + +@pytest.mark.parametrize( + "profits,starting_balance,expected_sqn,description", + [ + ([1.0, -0.5, 2.0, -1.0, 0.5, 1.5, -0.5, 1.0], 100, 1.3229, "Mixed profits/losses"), + ([], 100, 0.0, "Empty dataframe"), + ([1.0, 0.5, 2.0, 1.5, 0.8], 100, 4.3657, "All winning trades"), + ([-1.0, -0.5, -2.0, -1.5, -0.8], 100, -4.3657, "All losing trades"), + ([1.0], 100, -100, "Single trade"), + ], +) +def test_calculate_sqn_cases(profits, starting_balance, expected_sqn, description): + """ + Test SQN calculation with various scenarios: + """ + trades = DataFrame({"profit_abs": profits}) + sqn = calculate_sqn(trades, starting_balance=starting_balance) + + assert isinstance(sqn, float) + assert pytest.approx(sqn, rel=1e-4) == expected_sqn + + +@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 + (1000, 1010, 0, 0.0), # zero days + (-100, 100, 365, 0.0), # negative starting balance + ], +) +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, + -0.025782, + 0.010400, + 0.012374, + 0.012467, + 0.114741, + 0.010303, + 0.010088, + -0.033961, + 0.010680, + 0.010886, + -0.029274, + 0.011178, + 0.010693, + 0.010711, + ] + + dates = [dt_utc(2020, 1, 1) + timedelta(days=i) for i in range(len(values))] + df = DataFrame(zip(values, dates, strict=False), columns=["profit", "open_date"]) + # sort by profit and reset index + df = df.sort_values("profit").reset_index(drop=True) + df1 = df.copy() + drawdown = calculate_max_drawdown( + df, date_col="open_date", starting_balance=0.2, value_col="profit" + ) + # Ensure df has not been altered. + assert df.equals(df1) + + assert isinstance(drawdown.drawdown_abs, float) + assert isinstance(drawdown.relative_account_drawdown, float) + # High must be before low + assert drawdown.high_date < drawdown.low_date + # High value must be higher than low value + assert drawdown.high_value > drawdown.low_value + assert drawdown.drawdown_abs == 0.091755 + assert pytest.approx(drawdown.relative_account_drawdown) == 0.32129575 + + df = DataFrame(zip(values[:5], dates[:5], strict=False), columns=["profit", "open_date"]) + # No losing trade ... + drawdown = calculate_max_drawdown(df, date_col="open_date", value_col="profit") + assert drawdown.drawdown_abs == 0.0 + assert drawdown.low_value == 0.0 + assert drawdown.current_high_value >= 0.0 + assert drawdown.current_drawdown_abs == 0.0 + + df1 = DataFrame(zip(values[:5], dates[:5], strict=False), columns=["profit", "open_date"]) + df1.loc[:, "profit"] = df1["profit"] * -1 + # No winning trade ... + drawdown = calculate_max_drawdown(df1, date_col="open_date", value_col="profit") + assert drawdown.drawdown_abs == 0.055545 + assert drawdown.high_value == 0.0 + assert drawdown.current_high_value == 0.0 + assert drawdown.current_drawdown_abs == 0.055545 + + +@pytest.mark.parametrize( + "profits,relative,highd,lowdays,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, 500.0, 0.5), + ], +) +def test_calculate_max_drawdown_abs(profits, relative, highd, lowdays, 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 + """ + init_date = datetime(2020, 1, 1, tzinfo=UTC) + dates = [init_date + timedelta(days=i) for i in range(len(profits))] + df = DataFrame(zip(profits, dates, strict=False), 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 = 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.drawdown_abs, float) + assert isinstance(drawdown.relative_account_drawdown, float) + assert drawdown.high_date == init_date + timedelta(days=highd) + assert drawdown.low_date == init_date + timedelta(days=lowdays) + + # High must be before low + assert drawdown.high_date < drawdown.low_date + # High value must be higher than low value + assert drawdown.high_value > drawdown.low_value + assert drawdown.drawdown_abs == result + assert pytest.approx(drawdown.relative_account_drawdown) == result_rel From f3c84d6a3cde70b780b06fe22d5b7045d51932d8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Apr 2026 16:01:26 +0200 Subject: [PATCH 194/315] feat: calculate sharpe-ratio from historic balance snapshots --- freqtrade/data/metrics.py | 47 ++++++++++++++++++- .../optimize/optimize_reports/bt_output.py | 7 +++ .../optimize_reports/optimize_reports.py | 3 ++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index a9d3963d2..00101c5ea 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -390,10 +390,55 @@ def calculate_sharpe( # Define high (negative) sharpe ratio to be clear that this is NOT optimal. sharp_ratio = -100 - # print(expected_returns_mean, up_stdev, sharp_ratio) return sharp_ratio +def calculate_sharpe_from_balance( + balance_history: pd.DataFrame, + date_col: str = "date", + balance_col: str = "total_quote", +) -> float: + """ + Calculate sharpe ratio from historical balance snapshots. + + :param balance_history: DataFrame containing at least date and balance columns + :param date_col: Column containing timestamps + :param balance_col: Column containing historical balance values + :return: sharpe + """ + if ( + len(balance_history) == 0 + or date_col not in balance_history + or balance_col not in balance_history + ): + return 0.0 + + wallet = balance_history.loc[:, [date_col, balance_col]].copy() + wallet[date_col] = pd.to_datetime(wallet[date_col], utc=True, errors="coerce") + wallet = wallet.dropna(subset=[date_col, balance_col]).sort_values(date_col) + + if len(wallet) < 2: + return 0.0 + + # Sample balance to daily end-of-day values to normalize variable snapshot frequency. + daily_balance = wallet.set_index(date_col)[balance_col].resample("1D").last().dropna() + daily_returns = daily_balance.pct_change().dropna() + + if len(daily_returns) == 0: + return 0.0 + + expected_returns_mean = daily_returns.mean() + up_stdev = daily_returns.std(ddof=0) + + if up_stdev != 0 and not np.isnan(up_stdev): + sharp_ratio = expected_returns_mean / up_stdev * np.sqrt(365) + else: + # Define high (negative) sharpe ratio to be clear that this is NOT optimal. + sharp_ratio = -100 + + return float(sharp_ratio) + + def calculate_calmar( trades: pd.DataFrame, min_date: datetime | None, diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index 23963038c..149f886a2 100644 --- a/freqtrade/optimize/optimize_reports/bt_output.py +++ b/freqtrade/optimize/optimize_reports/bt_output.py @@ -308,6 +308,13 @@ def text_table_add_metrics(strat_results: dict) -> None: ), ] ) + if "sharpe" in wallet_stats: + wallet_metrics.append( + ( + "Sharpe ratio balance", + f"{wallet_stats['sharpe']:.2f}", + ) + ) # 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 diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index ba84d8834..24c683482 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -15,6 +15,7 @@ from freqtrade.data.metrics import ( calculate_market_change, calculate_max_drawdown, calculate_sharpe, + calculate_sharpe_from_balance, calculate_sortino, calculate_sqn, ) @@ -59,11 +60,13 @@ def generate_wallet_stats(wallet_df: DataFrame, stake_currency: str) -> dict[str low_balance = total_quote.loc[low_idx] low_date = wallet.loc[low_idx, "date"] high_date = wallet.loc[high_idx, "date"] + sharpe = calculate_sharpe_from_balance(wallet) return { "start_balance": start_balance, "end_balance": end_balance, "high_balance": high_balance, "low_balance": low_balance, + "sharpe": sharpe, "low_date": low_date.strftime(DATETIME_PRINT_FORMAT), "low_ts": int(low_date.timestamp() * 1000), "high_date": high_date.strftime(DATETIME_PRINT_FORMAT), From 97badd0d3b7b9763632441e5368acd468ba49235 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Apr 2026 16:01:42 +0200 Subject: [PATCH 195/315] test: add tests for sharpe based on balance --- tests/data/test_metrics.py | 43 +++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/tests/data/test_metrics.py b/tests/data/test_metrics.py index d19d9e328..38ce57aff 100644 --- a/tests/data/test_metrics.py +++ b/tests/data/test_metrics.py @@ -1,7 +1,8 @@ from datetime import UTC, datetime, timedelta +import numpy as np import pytest -from pandas import DataFrame, DateOffset, Timestamp +from pandas import DataFrame, DateOffset, Timestamp, to_datetime from freqtrade.configuration import TimeRange from freqtrade.data.btanalysis import ( @@ -16,6 +17,7 @@ from freqtrade.data.metrics import ( calculate_market_change, calculate_max_drawdown, calculate_sharpe, + calculate_sharpe_from_balance, calculate_sortino, calculate_sqn, calculate_underwater, @@ -217,6 +219,45 @@ def test_calculate_sharpe(testdatadir): assert pytest.approx(sharpe) == 44.5078669 +def test_calculate_sharpe_from_balance(): + balance_history = DataFrame( + { + "date": to_datetime( + [ + "2025-01-01 00:00:00+00:00", + "2025-01-02 00:00:00+00:00", + "2025-01-03 00:00:00+00:00", + "2025-01-04 00:00:00+00:00", + ], + utc=True, + ), + "total_quote": [100.0, 110.0, 104.5, 125.4], + } + ) + + sharpe = calculate_sharpe_from_balance(balance_history) + expected_returns = np.array([0.1, -0.05, 0.2]) + expected_sharpe = expected_returns.mean() / expected_returns.std() * np.sqrt(365) + + assert isinstance(sharpe, float) + assert pytest.approx(sharpe) == expected_sharpe + + +def test_calculate_sharpe_from_balance_empty_or_flat(): + assert calculate_sharpe_from_balance(DataFrame()) == 0.0 + + flat_balance_history = DataFrame( + { + "date": to_datetime( + ["2025-01-01 00:00:00+00:00", "2025-01-02 00:00:00+00:00"], + utc=True, + ), + "total_quote": [100.0, 100.0], + } + ) + assert calculate_sharpe_from_balance(flat_balance_history) == -100 + + def test_calculate_calmar(testdatadir): filename = testdatadir / "backtest_results/backtest-result.json" bt_data = load_backtest_data(filename) From 017df564ce367d1c7d2304374621cc61273e8a6f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Apr 2026 16:05:21 +0200 Subject: [PATCH 196/315] refactor: extract annualizated ratio calculation --- freqtrade/data/metrics.py | 54 +++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index 00101c5ea..5538b72e5 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -333,6 +333,25 @@ def calculate_expectancy(trades: pd.DataFrame) -> tuple[float, float]: return expectancy, expectancy_ratio +def _calculate_annualized_ratio( + expected_returns_mean: float, + denominator: float, + annualization_factor: int = 365, +) -> float: + """ + Helper function to calculate annualized ratios like Sharpe and Sortino. + :param expected_returns_mean: Mean of the returns (expected returns) + :param denominator: Denominator of the ratio (e.g. standard deviation for Sharpe) + :param annualization_factor: Factor to annualize the ratio (default is 365 for daily returns) + :return: Annualized ratio, or -100.0 if denominator is zero or NaN to indicate this is + not optimal. + """ + if denominator != 0 and not np.isnan(denominator): + return float(expected_returns_mean / denominator * np.sqrt(annualization_factor)) + + # Define high (negative) ratio to be clear that this is NOT optimal. + return -100.0 + def calculate_sortino( trades: pd.DataFrame, min_date: datetime | None, @@ -354,14 +373,7 @@ def calculate_sortino( down_stdev = np.std(trades.loc[trades["profit_abs"] < 0, "profit_abs"] / starting_balance) - if down_stdev != 0 and not np.isnan(down_stdev): - sortino_ratio = expected_returns_mean / down_stdev * np.sqrt(365) - else: - # Define high (negative) sortino ratio to be clear that this is NOT optimal. - sortino_ratio = -100 - - # print(expected_returns_mean, down_stdev, sortino_ratio) - return sortino_ratio + return _calculate_annualized_ratio(expected_returns_mean, down_stdev) def calculate_sharpe( @@ -384,13 +396,7 @@ def calculate_sharpe( expected_returns_mean = total_profit.sum() / days_period up_stdev = np.std(total_profit) - if up_stdev != 0: - sharp_ratio = expected_returns_mean / up_stdev * np.sqrt(365) - else: - # Define high (negative) sharpe ratio to be clear that this is NOT optimal. - sharp_ratio = -100 - - return sharp_ratio + return _calculate_annualized_ratio(expected_returns_mean, up_stdev) def calculate_sharpe_from_balance( @@ -429,14 +435,7 @@ def calculate_sharpe_from_balance( expected_returns_mean = daily_returns.mean() up_stdev = daily_returns.std(ddof=0) - - if up_stdev != 0 and not np.isnan(up_stdev): - sharp_ratio = expected_returns_mean / up_stdev * np.sqrt(365) - else: - # Define high (negative) sharpe ratio to be clear that this is NOT optimal. - sharp_ratio = -100 - - return float(sharp_ratio) + return _calculate_annualized_ratio(expected_returns_mean, up_stdev) def calculate_calmar( @@ -469,14 +468,7 @@ def calculate_calmar( except ValueError: max_drawdown = 0 - if max_drawdown != 0: - calmar_ratio = expected_returns_mean / max_drawdown * math.sqrt(365) - else: - # Define high (negative) calmar ratio to be clear that this is NOT optimal. - calmar_ratio = -100 - - # print(expected_returns_mean, max_drawdown, calmar_ratio) - return calmar_ratio + return _calculate_annualized_ratio(expected_returns_mean, max_drawdown) def calculate_sqn(trades: pd.DataFrame, starting_balance: float) -> float: From c2b95090f7d3187e2ae99feb196476c288c4e170 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Apr 2026 16:07:55 +0200 Subject: [PATCH 197/315] refactor: reusable "daily_returns_from_balance" method --- freqtrade/data/metrics.py | 42 +++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index 5538b72e5..b766f461d 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -352,6 +352,30 @@ def _calculate_annualized_ratio( # Define high (negative) ratio to be clear that this is NOT optimal. return -100.0 + +def _calculate_daily_returns_from_balance( + balance_history: pd.DataFrame, + date_col: str, + balance_col: str, +) -> pd.Series: + if ( + len(balance_history) == 0 + or date_col not in balance_history + or balance_col not in balance_history + ): + return pd.Series(dtype=float) + + wallet = balance_history.loc[:, [date_col, balance_col]].copy() + wallet = wallet.dropna(subset=[date_col, balance_col]).sort_values(date_col) + + if len(wallet) < 2: + return pd.Series(dtype=float) + + # Sample balance to daily end-of-day values to normalize variable snapshot frequency. + daily_balance = wallet.set_index(date_col)[balance_col].resample("1D").last().dropna() + return daily_balance.pct_change().dropna() + + def calculate_sortino( trades: pd.DataFrame, min_date: datetime | None, @@ -412,23 +436,7 @@ def calculate_sharpe_from_balance( :param balance_col: Column containing historical balance values :return: sharpe """ - if ( - len(balance_history) == 0 - or date_col not in balance_history - or balance_col not in balance_history - ): - return 0.0 - - wallet = balance_history.loc[:, [date_col, balance_col]].copy() - wallet[date_col] = pd.to_datetime(wallet[date_col], utc=True, errors="coerce") - wallet = wallet.dropna(subset=[date_col, balance_col]).sort_values(date_col) - - if len(wallet) < 2: - return 0.0 - - # Sample balance to daily end-of-day values to normalize variable snapshot frequency. - daily_balance = wallet.set_index(date_col)[balance_col].resample("1D").last().dropna() - daily_returns = daily_balance.pct_change().dropna() + daily_returns = _calculate_daily_returns_from_balance(balance_history, date_col, balance_col) if len(daily_returns) == 0: return 0.0 From 6210927bdb984670844df99e18cf0affc50281ea Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Apr 2026 16:37:37 +0200 Subject: [PATCH 198/315] feat: add sortino_from_balance calculation --- freqtrade/data/metrics.py | 24 +++++++++++++++++++ tests/data/test_metrics.py | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index b766f461d..e4e618d1c 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -400,6 +400,30 @@ def calculate_sortino( return _calculate_annualized_ratio(expected_returns_mean, down_stdev) +def calculate_sortino_from_balance( + balance_history: pd.DataFrame, + date_col: str = "date", + balance_col: str = "total_quote", +) -> float: + """ + Calculate sortino ratio from historical balance snapshots. + + :param balance_history: DataFrame containing at least date and balance columns + :param date_col: Column containing timestamps + :param balance_col: Column containing historical balance values + :return: sortino + """ + daily_returns = _calculate_daily_returns_from_balance(balance_history, date_col, balance_col) + + if len(daily_returns) == 0: + return 0.0 + + expected_returns_mean = daily_returns.mean() + downside_returns = daily_returns[daily_returns < 0] + down_stdev = downside_returns.std(ddof=0) + return _calculate_annualized_ratio(expected_returns_mean, down_stdev) + + def calculate_sharpe( trades: pd.DataFrame, min_date: datetime | None, diff --git a/tests/data/test_metrics.py b/tests/data/test_metrics.py index 38ce57aff..53f8ef0be 100644 --- a/tests/data/test_metrics.py +++ b/tests/data/test_metrics.py @@ -19,6 +19,7 @@ from freqtrade.data.metrics import ( calculate_sharpe, calculate_sharpe_from_balance, calculate_sortino, + calculate_sortino_from_balance, calculate_sqn, calculate_underwater, combine_dataframes_with_mean, @@ -202,6 +203,53 @@ def test_calculate_sortino(testdatadir): assert pytest.approx(sortino) == 35.17722 +def test_calculate_sortino_from_balance(): + balance_history = DataFrame( + { + "date": to_datetime( + [ + "2025-01-01 00:00:00+00:00", + "2025-01-02 00:00:00+00:00", + "2025-01-03 00:00:00+00:00", + "2025-01-04 00:00:00+00:00", + "2025-01-05 00:00:00+00:00", + ], + utc=True, + ), + "total_quote": [100.0, 110.0, 104.5, 125.4, 112.86], + } + ) + + sortino = calculate_sortino_from_balance(balance_history) + expected_returns = np.array([0.1, -0.05, 0.2, -0.1]) + expected_sortino = expected_returns.mean() / np.std(expected_returns[expected_returns < 0]) + expected_sortino *= np.sqrt(365) + + assert isinstance(sortino, float) + assert pytest.approx(sortino) == expected_sortino + # Explicit assert + assert pytest.approx(sortino) == 28.6574597 + + +def test_calculate_sortino_from_balance_empty_or_no_downside(): + assert calculate_sortino_from_balance(DataFrame()) == 0.0 + + positive_balance_history = DataFrame( + { + "date": to_datetime( + [ + "2025-01-01 00:00:00+00:00", + "2025-01-02 00:00:00+00:00", + "2025-01-03 00:00:00+00:00", + ], + utc=True, + ), + "total_quote": [100.0, 110.0, 121.0], + } + ) + assert calculate_sortino_from_balance(positive_balance_history) == -100 + + def test_calculate_sharpe(testdatadir): filename = testdatadir / "backtest_results/backtest-result.json" bt_data = load_backtest_data(filename) From fba843cb61a0edd2636e79d7db5cf57caf8c1eb7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Apr 2026 17:05:55 +0200 Subject: [PATCH 199/315] refactor: extract balance df checks for future reuse --- freqtrade/data/metrics.py | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index e4e618d1c..8573505c7 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -358,22 +358,45 @@ def _calculate_daily_returns_from_balance( date_col: str, balance_col: str, ) -> pd.Series: + wallet = _prepare_balance_history(balance_history, date_col, balance_col) + if len(wallet) == 0: + return pd.DataFrame(columns=[date_col, balance_col]) + + # Sample balance to daily end-of-day values to normalize variable snapshot frequency. + daily_balance = ( + wallet.set_index(date_col)[balance_col].resample("1D").last().dropna().rename(balance_col) + ) + daily_balance = daily_balance.reset_index() + + if len(daily_balance) < 2: + return pd.Series(dtype=float) + + return daily_balance[balance_col].pct_change().dropna() + + +def _prepare_balance_history( + balance_history: pd.DataFrame, + date_col: str, + balance_col: str, +) -> pd.DataFrame: + """ + Prepare balance history for calculations by filtering out rows with + missing date or balance values. + """ if ( len(balance_history) == 0 or date_col not in balance_history or balance_col not in balance_history ): - return pd.Series(dtype=float) + return pd.DataFrame(columns=[date_col, balance_col]) wallet = balance_history.loc[:, [date_col, balance_col]].copy() wallet = wallet.dropna(subset=[date_col, balance_col]).sort_values(date_col) - if len(wallet) < 2: - return pd.Series(dtype=float) + if len(wallet) == 0: + return pd.DataFrame(columns=[date_col, balance_col]) - # Sample balance to daily end-of-day values to normalize variable snapshot frequency. - daily_balance = wallet.set_index(date_col)[balance_col].resample("1D").last().dropna() - return daily_balance.pct_change().dropna() + return wallet def calculate_sortino( From bcd9023a8d32a6af702ee25c3038950b356f088a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Apr 2026 17:09:37 +0200 Subject: [PATCH 200/315] feat: add max-drawdown from wallet balance --- freqtrade/data/metrics.py | 37 ++++++++++++++++++++++++++++++++ tests/data/test_metrics.py | 43 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index 8573505c7..5aaf6fac2 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -493,6 +493,43 @@ def calculate_sharpe_from_balance( return _calculate_annualized_ratio(expected_returns_mean, up_stdev) +def calculate_max_drawdown_from_balance( + balance_history: pd.DataFrame, + date_col: str = "date", + balance_col: str = "total_quote", + relative: bool = False, +) -> DrawDownResult: + """ + Calculate max drawdown from historical balance snapshots. + + :param balance_history: DataFrame containing at least date and balance columns + :param date_col: Column containing timestamps + :param balance_col: Column containing historical balance values + :param relative: If True, use relative drawdown for max calculation instead of absolute + :return: DrawDownResult object + :raise: ValueError if balance-history dataframe was found empty. + """ + wallet = _prepare_balance_history( + balance_history=balance_history, + date_col=date_col, + balance_col=balance_col, + ) + + if len(wallet) < 2: + raise ValueError("Balance-history dataframe empty.") + + starting_balance = float(wallet[balance_col].iloc[0]) + wallet.loc[:, "total_balance"] = wallet[balance_col].diff().fillna(0.0) + + return calculate_max_drawdown( + wallet, + date_col=date_col, + value_col="total_balance", + starting_balance=starting_balance, + relative=relative, + ) + + def calculate_calmar( trades: pd.DataFrame, min_date: datetime | None, diff --git a/tests/data/test_metrics.py b/tests/data/test_metrics.py index 53f8ef0be..610cbd8ee 100644 --- a/tests/data/test_metrics.py +++ b/tests/data/test_metrics.py @@ -16,6 +16,7 @@ from freqtrade.data.metrics import ( calculate_expectancy, calculate_market_change, calculate_max_drawdown, + calculate_max_drawdown_from_balance, calculate_sharpe, calculate_sharpe_from_balance, calculate_sortino, @@ -145,6 +146,48 @@ def test_calculate_max_drawdown(testdatadir): calculate_underwater(DataFrame()) +def test_calculate_max_drawdown_from_balance(): + balance_history = DataFrame( + { + "date": to_datetime( + [ + "2025-01-01 00:00:00+00:00", + "2025-01-01 12:00:00+00:00", + "2025-01-01 18:00:00+00:00", + "2025-01-04 00:00:00+00:00", + ], + utc=True, + ), + "total_quote": [100.0, 120.0, 80.0, 110.0], + } + ) + + drawdown = calculate_max_drawdown_from_balance(balance_history) + assert isinstance(drawdown.relative_account_drawdown, float) + assert pytest.approx(drawdown.relative_account_drawdown) == 1 / 3 + assert pytest.approx(drawdown.drawdown_abs) == 40 + assert pytest.approx(drawdown.current_high_value) == 20 + assert pytest.approx(drawdown.low_value) == -20 + assert pytest.approx(drawdown.high_value) == 20 + + assert drawdown.high_date == Timestamp("2025-01-01 12:00:00", tz="UTC") + assert drawdown.low_date == Timestamp("2025-01-01 18:00:00", tz="UTC") + + +def test_calculate_max_drawdown_from_balance_empty_or_short(): + with pytest.raises(ValueError, match=r"Balance-history dataframe empty\."): + calculate_max_drawdown_from_balance(DataFrame()) + + one_point = DataFrame( + { + "date": to_datetime(["2025-01-01 00:00:00+00:00"], utc=True), + "total_quote": [100.0], + } + ) + with pytest.raises(ValueError, match=r"Balance-history dataframe empty\."): + calculate_max_drawdown_from_balance(one_point) + + def test_calculate_csum(testdatadir): filename = testdatadir / "backtest_results/backtest-result.json" bt_data = load_backtest_data(filename) From 624be0c469c0f06fab999412d37e3794acd51bbb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Apr 2026 17:10:05 +0200 Subject: [PATCH 201/315] feat: add calmar_from_balance --- freqtrade/data/metrics.py | 48 +++++++++++++++++++++++++++++++++++--- tests/data/test_metrics.py | 40 +++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 3 deletions(-) diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index 5aaf6fac2..4d66104ea 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -537,12 +537,12 @@ def calculate_calmar( starting_balance: float, ) -> float: """ - Calculate calmar + Calculate calmar from trades data. :param trades: DataFrame containing trades (requires columns close_date and profit_abs) :return: calmar """ if (len(trades) == 0) or (min_date is None) or (max_date is None) or (min_date == max_date): - return 0 + return 0.0 total_profit = trades["profit_abs"].sum() / starting_balance days_period = max(1, (max_date - min_date).days) @@ -558,7 +558,49 @@ def calculate_calmar( ) max_drawdown = drawdown.relative_account_drawdown except ValueError: - max_drawdown = 0 + return 0.0 + + return _calculate_annualized_ratio(expected_returns_mean, max_drawdown) + + +def calculate_calmar_from_balance( + balance_history: pd.DataFrame, + date_col: str = "date", + balance_col: str = "total_quote", +) -> float: + """ + Calculate calmar ratio from historical balance snapshots. + + :param balance_history: DataFrame containing at least date and balance columns + :param date_col: Column containing timestamps + :param balance_col: Column containing historical balance values + :return: calmar + """ + wallet = _prepare_balance_history( + balance_history=balance_history, + date_col=date_col, + balance_col=balance_col, + ) + + if len(wallet) < 2: + return 0.0 + + starting_balance = float(wallet[balance_col].iloc[0]) + final_balance = float(wallet[balance_col].iloc[-1]) + days_period = max(1, (wallet[date_col].iloc[-1] - wallet[date_col].iloc[0]).days) + + total_profit = (final_balance - starting_balance) / starting_balance + expected_returns_mean = total_profit / days_period * 100 + + try: + drawdown = calculate_max_drawdown_from_balance( + wallet, + date_col=date_col, + balance_col=balance_col, + ) + max_drawdown = drawdown.relative_account_drawdown + except ValueError: + return 0.0 return _calculate_annualized_ratio(expected_returns_mean, max_drawdown) diff --git a/tests/data/test_metrics.py b/tests/data/test_metrics.py index 610cbd8ee..242700eef 100644 --- a/tests/data/test_metrics.py +++ b/tests/data/test_metrics.py @@ -12,6 +12,7 @@ from freqtrade.data.history import load_data, load_pair_history from freqtrade.data.metrics import ( calculate_cagr, calculate_calmar, + calculate_calmar_from_balance, calculate_csum, calculate_expectancy, calculate_market_change, @@ -366,6 +367,45 @@ def test_calculate_calmar(testdatadir): assert pytest.approx(calmar) == 559.040508 +def test_calculate_calmar_from_balance(): + balance_history = DataFrame( + { + "date": to_datetime( + [ + "2025-01-01 00:00:00+00:00", + "2025-01-01 12:00:00+00:00", + "2025-01-01 18:00:00+00:00", + "2025-01-04 00:00:00+00:00", + ], + utc=True, + ), + "total_quote": [100.0, 120.0, 80.0, 110.0], + } + ) + + calmar = calculate_calmar_from_balance(balance_history) + expected_returns_mean = ((110.0 - 100.0) / 100.0) / 3 * 100 + expected_calmar = expected_returns_mean / (1 / 3) * np.sqrt(365) + + assert isinstance(calmar, float) + assert pytest.approx(calmar) == expected_calmar + + +def test_calculate_calmar_from_balance_empty_or_flat(): + assert calculate_calmar_from_balance(DataFrame()) == 0.0 + + flat_balance_history = DataFrame( + { + "date": to_datetime( + ["2025-01-01 00:00:00+00:00", "2025-01-02 00:00:00+00:00"], + utc=True, + ), + "total_quote": [100.0, 100.0], + } + ) + assert calculate_calmar_from_balance(flat_balance_history) == -100 + + def test_calculate_sqn(testdatadir): filename = testdatadir / "backtest_results/backtest-result.json" bt_data = load_backtest_data(filename) From bc0c8ffb712d97c2f3f33a0bfe5815b1fc152d78 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Apr 2026 17:25:45 +0200 Subject: [PATCH 202/315] feat: add wallet based metrics to backtest output --- .../optimize/optimize_reports/bt_output.py | 32 ++++++++++++----- .../optimize_reports/optimize_reports.py | 34 +++++++++++++++++++ 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index 149f886a2..c8b4b50db 100644 --- a/freqtrade/optimize/optimize_reports/bt_output.py +++ b/freqtrade/optimize/optimize_reports/bt_output.py @@ -289,7 +289,7 @@ def text_table_add_metrics(strat_results: dict) -> None: ) wallet_metrics: list[tuple[str, str]] = [ ( - "Min/Max balance realized", + "Min/Max balance (realized)", f"{fmt_coin(strat_results['csum_min'], stake)} / " f"{fmt_coin(strat_results['csum_max'], stake)}", ), @@ -298,22 +298,38 @@ def text_table_add_metrics(strat_results: dict) -> None: wallet_metrics.extend( [ ( - "Min/Max balance unrealized", + "Min/Max balance (unrealized)", f"{fmt_coin(wallet_stats['low_balance'], stake)} / " f"{fmt_coin(wallet_stats['high_balance'], stake)}", ), ( - "Min/Max balance dates", + "Min/Max balance dates (unrealized)", f"{wallet_stats['low_date']} / {wallet_stats['high_date']}", ), ] ) if "sharpe" in wallet_stats: - wallet_metrics.append( - ( - "Sharpe ratio balance", - f"{wallet_stats['sharpe']:.2f}", - ) + # Assume that if sharpe is there, all others are there as well. + wallet_metrics.extend( + [ + ( + "Sharpe (unrealized)", + f"{wallet_stats['sharpe']:.2f}", + ), + ( + "Sortino (unrealized)", + f"{wallet_stats['sortino']:.2f}", + ), + ( + "Calmar (unrealized)", + f"{wallet_stats['calmar']:.2f}", + ), + ( + "Max drawdown (unrealized)", + f"{fmt_coin(wallet_stats['max_drawdown_abs'], stake)} " + f"({wallet_stats['max_drawdown_account']:.2%})", + ), + ] ) # Newly added fields should be ignored if they are missing in strat_results. hyperopt-show diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index 24c683482..a17d92b59 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -10,13 +10,16 @@ from freqtrade.constants import BACKTEST_BREAKDOWNS, DATETIME_PRINT_FORMAT from freqtrade.data.metrics import ( calculate_cagr, calculate_calmar, + calculate_calmar_from_balance, calculate_csum, calculate_expectancy, calculate_market_change, calculate_max_drawdown, + calculate_max_drawdown_from_balance, calculate_sharpe, calculate_sharpe_from_balance, calculate_sortino, + calculate_sortino_from_balance, calculate_sqn, ) from freqtrade.ft_types import ( @@ -61,12 +64,43 @@ def generate_wallet_stats(wallet_df: DataFrame, stake_currency: str) -> dict[str low_date = wallet.loc[low_idx, "date"] high_date = wallet.loc[high_idx, "date"] sharpe = calculate_sharpe_from_balance(wallet) + sortino = calculate_sortino_from_balance(wallet) + calmar = calculate_calmar_from_balance(wallet) + try: + drawdown = calculate_max_drawdown_from_balance(wallet) + except ValueError: + drawdown = None + return { "start_balance": start_balance, "end_balance": end_balance, "high_balance": high_balance, "low_balance": low_balance, "sharpe": sharpe, + "sortino": sortino, + "calmar": calmar, + "max_drawdown_account": drawdown.relative_account_drawdown if drawdown else 0.0, + "max_drawdown_abs": drawdown.drawdown_abs if drawdown else 0.0, + "drawdown_start": ( + drawdown.high_date.strftime(DATETIME_PRINT_FORMAT) + if drawdown and drawdown.high_date is not None + else None + ), + "drawdown_start_ts": ( + int(drawdown.high_date.timestamp() * 1000) + if drawdown and drawdown.high_date is not None + else None + ), + "drawdown_end": ( + drawdown.low_date.strftime(DATETIME_PRINT_FORMAT) + if drawdown and drawdown.low_date is not None + else None + ), + "drawdown_end_ts": ( + int(drawdown.low_date.timestamp() * 1000) + if drawdown and drawdown.low_date is not None + else None + ), "low_date": low_date.strftime(DATETIME_PRINT_FORMAT), "low_ts": int(low_date.timestamp() * 1000), "high_date": high_date.strftime(DATETIME_PRINT_FORMAT), From 64ceb028ac60f642a864786e9c5c58b9f201e45e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Apr 2026 17:25:58 +0200 Subject: [PATCH 203/315] test: add test for wallet based output --- tests/optimize/test_optimize_reports.py | 54 +++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index b37080f43..2d82be360 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -28,6 +28,7 @@ from freqtrade.optimize.optimize_reports import ( generate_trading_stats, show_sorted_pairlist, store_backtest_results, + text_table_add_metrics, text_table_bt_results, text_table_strategy, ) @@ -36,6 +37,7 @@ from freqtrade.optimize.optimize_reports.optimize_reports import ( _get_resample_from_period, calc_streak, generate_tag_metrics, + generate_wallet_stats, ) from freqtrade.resolvers.strategy_resolver import StrategyResolver from freqtrade.util import dt_ts, format_duration @@ -616,6 +618,58 @@ def test_text_table_strategy(testdatadir, capsys): ) +def test_generate_wallet_stats_extended_metrics(): + wallet_df = pd.DataFrame( + { + "date": [ + dt_utc(2025, 1, 1, 0, 0, 0), + dt_utc(2025, 1, 1, 12, 0, 0), + dt_utc(2025, 1, 1, 18, 0, 0), + dt_utc(2025, 1, 3, 0, 0, 0), + ], + "currency": ["BTC", "BTC", "BTC", "BTC"], + "rate": [1.0, 1.0, 1.0, 1.0], + "balance": [100.0, 120.0, 80.0, 110.0], + } + ) + + stats = generate_wallet_stats(wallet_df, "BTC") + + assert "sharpe" in stats + assert "sortino" in stats + assert "calmar" in stats + assert "max_drawdown_account" in stats + assert "max_drawdown_abs" in stats + assert pytest.approx(stats["max_drawdown_account"]) == 1 / 3 + assert stats["drawdown_start"] == "2025-01-01 12:00:00" + assert stats["drawdown_end"] == "2025-01-01 18:00:00" + + +def test_text_table_add_metrics_shows_wallet_ratios(testdatadir, capsys): + filename = testdatadir / "backtest_results/backtest-result.json" + bt_data = load_backtest_stats(filename) + strat_results = next(iter(bt_data["strategy"].values())) + strat_results["wallet_stats"] = { + "low_balance": 0.95, + "high_balance": 1.12, + "low_date": "2025-01-01 18:00:00", + "high_date": "2025-01-01 12:00:00", + "sharpe": 1.23, + "sortino": 2.34, + "calmar": 3.45, + "max_drawdown_account": 0.12, + "max_drawdown_abs": 0.05, + } + + text_table_add_metrics(strat_results) + text = capsys.readouterr().out + + assert "Sharpe ratio balance" in text + assert "Sortino ratio balance" in text + assert "Calmar ratio balance" in text + assert "Max drawdown balance" in text + + def test_generate_periodic_breakdown_stats(testdatadir): filename = testdatadir / "backtest_results/backtest-result.json" bt_data = load_backtest_data(filename).to_dict(orient="records") From 5e0eb5da1079595a8e11ceab59631b25485796ed Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 11 Apr 2026 18:15:36 +0200 Subject: [PATCH 204/315] chore: improved backtest-output ordering --- .../optimize/optimize_reports/bt_output.py | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index c8b4b50db..148583ed7 100644 --- a/freqtrade/optimize/optimize_reports/bt_output.py +++ b/freqtrade/optimize/optimize_reports/bt_output.py @@ -294,7 +294,8 @@ def text_table_add_metrics(strat_results: dict) -> None: f"{fmt_coin(strat_results['csum_max'], stake)}", ), ] - if wallet_stats := strat_results.get("wallet_stats"): + wallet_stats = strat_results.get("wallet_stats", {}) + if wallet_stats: wallet_metrics.extend( [ ( @@ -308,24 +309,12 @@ def text_table_add_metrics(strat_results: dict) -> None: ), ] ) - if "sharpe" in wallet_stats: + if "max_drawdown_abs" in wallet_stats: # Assume that if sharpe is there, all others are there as well. - wallet_metrics.extend( + drawdown_metrics.extend( [ ( - "Sharpe (unrealized)", - f"{wallet_stats['sharpe']:.2f}", - ), - ( - "Sortino (unrealized)", - f"{wallet_stats['sortino']:.2f}", - ), - ( - "Calmar (unrealized)", - f"{wallet_stats['calmar']:.2f}", - ), - ( - "Max drawdown (unrealized)", + "Absolute drawdown (unrealized)", f"{fmt_coin(wallet_stats['max_drawdown_abs'], stake)} " f"({wallet_stats['max_drawdown_account']:.2%})", ), @@ -359,9 +348,27 @@ def text_table_add_metrics(strat_results: dict) -> None: ), ("Total profit %", f"{strat_results['profit_total']:.2%}"), ("CAGR %", f"{strat_results['cagr']:.2%}" if "cagr" in strat_results else "N/A"), - ("Sortino", f"{strat_results['sortino']:.2f}" if "sortino" in strat_results else "N/A"), ("Sharpe", f"{strat_results['sharpe']:.2f}" if "sharpe" in strat_results else "N/A"), + ( + "Sharpe (unrealized)", + f"{wallet_stats['sharpe']:.2f}" + if wallet_stats and "sharpe" in wallet_stats + else "N/A", + ), + ("Sortino", f"{strat_results['sortino']:.2f}" if "sortino" in strat_results else "N/A"), + ( + "Sortino (unrealized)", + f"{wallet_stats['sortino']:.2f}" + if wallet_stats and "sortino" in wallet_stats + else "N/A", + ), ("Calmar", f"{strat_results['calmar']:.2f}" if "calmar" in strat_results else "N/A"), + ( + "Calmar (unrealized)", + f"{wallet_stats['calmar']:.2f}" + if wallet_stats and "calmar" in wallet_stats + else "N/A", + ), ("SQN", f"{strat_results['sqn']:.2f}" if "sqn" in strat_results else "N/A"), ( "Profit factor", From 81fcd5ea5adee9c7c85b241facc0d69006ab5086 Mon Sep 17 00:00:00 2001 From: ABS <53243996+ABSllk@users.noreply.github.com> Date: Sun, 12 Apr 2026 02:42:35 +0800 Subject: [PATCH 205/315] fix(bitget): use stopLossPrice mapping for futures stoploss handling --- freqtrade/exchange/bitget.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/exchange/bitget.py b/freqtrade/exchange/bitget.py index 72aaf44cb..9691f72f8 100644 --- a/freqtrade/exchange/bitget.py +++ b/freqtrade/exchange/bitget.py @@ -38,6 +38,8 @@ class Bitget(Exchange): _ft_has_futures: FtHas = { "funding_fee_candle_limit": 100, "has_delisting": True, + "stop_price_param": "stopLossPrice", + "stop_price_prop": "stopLossPrice", "stop_price_type_field": "triggerType", "stop_price_type_value_mapping": { PriceType.LAST: "fill_price", From dfa2db8575b7fdf3ad5b76414aeceaabf763b22c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 08:34:22 +0200 Subject: [PATCH 206/315] docs: add new fields to the docs --- docs/backtesting.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 12455b367..eaf3d1581 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -394,9 +394,12 @@ It contains key metrics about the performance of your strategy on backtesting da - `Absolute profit`: Profit made in stake currency. - `Total profit %`: Total profit. Aligned to the `TOTAL` row's `Tot Profit %` from the first table. Calculated as `(End capital − Starting capital) / Starting capital`. - `CAGR %`: Compound annual growth rate. -- `Sortino`: Annualized Sortino ratio. -- `Sharpe`: Annualized Sharpe ratio. -- `Calmar`: Annualized Calmar ratio. +- `Sharpe (closed trades)`: Annualized Sharpe ratio including only closed trades (ignoring open trades with profits or losses). +- `Sharpe (wallet balance)` Annualized Sharpe ratio calculation but including unrealized profits. +- `Sortino (closed trades)`: Annualized Sortino ratio including only closed trades (ignoring open trades with profits or losses). +- `Sortino (wallet balance)` Annualized Sortino ratio calculation but including unrealized profits. +- `Calmar (closed trades)`: Annualized Calmar ratio including only closed trades (ignoring open trades with profits or losses). +- `Calmar (wallet balance)` Annualized Calmar ratio calculation but including unrealized profits. - `SQN`: System Quality Number (SQN) - by Van Tharp. - `Profit factor`: Sum of the profits of all winning trades divided by the sum of the losses of all losing trades. - `Expectancy (Ratio)`: Expectancy ratio, which is the average profit or loss per trade. A negative expectancy ratio means that your strategy is not profitable. @@ -415,11 +418,12 @@ It contains key metrics about the performance of your strategy on backtesting da - `Max Consecutive Wins / Loss`: Maximum consecutive wins/losses in a row. - `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/Max balance realized`: Lowest and Highest Wallet balance during the backtest period based on closed trades trades. -- `Min/Max balance unrealized`: Lowest and Highest Wallet balance during the backtest period - including capital tied in open trades. -- `Min/Max balance dates`: Dates when the minimum and maximum unrealized balance occurred. +- `Min/Max balance (closed trades)`: Lowest and Highest Wallet balance during the backtest period based on closed trades trades. +- `Min/Max balance (wallet balance)`: Lowest and Highest Wallet balance during the backtest period - including capital tied in open trades. +- `Min/Max balance dates (wallet balance)`: Dates when the minimum and maximum unrealized balance occurred. - `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`: Maximum absolute drawdown experienced, including percentage relative to the account calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.. +- `Absolute drawdown (wallet balance)`: Maximum absolute drawdown experienced based on the unrealized balance, including percentage relative to the account calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`. - `Drawdown duration`: Duration of the largest drawdown period. - `Profit at drawdown start` / `Profit at drawdown end`: Profit at the beginning and end of the largest drawdown period. - `Drawdown start` / `Drawdown end`: Start and end datetime for the largest drawdown (can also be visualized via the `plot-dataframe` sub-command). From 173c75c08c15232e177b1855387c0b982d55b72f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 09:00:14 +0200 Subject: [PATCH 207/315] feat: improve wording on metrics --- .../optimize/optimize_reports/bt_output.py | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index 148583ed7..4f8143420 100644 --- a/freqtrade/optimize/optimize_reports/bt_output.py +++ b/freqtrade/optimize/optimize_reports/bt_output.py @@ -289,7 +289,7 @@ def text_table_add_metrics(strat_results: dict) -> None: ) wallet_metrics: list[tuple[str, str]] = [ ( - "Min/Max balance (realized)", + "Min/Max balance (closed trades)", f"{fmt_coin(strat_results['csum_min'], stake)} / " f"{fmt_coin(strat_results['csum_max'], stake)}", ), @@ -299,26 +299,25 @@ def text_table_add_metrics(strat_results: dict) -> None: wallet_metrics.extend( [ ( - "Min/Max balance (unrealized)", + "Min/Max balance (wallet balance)", f"{fmt_coin(wallet_stats['low_balance'], stake)} / " f"{fmt_coin(wallet_stats['high_balance'], stake)}", ), ( - "Min/Max balance dates (unrealized)", + "Min/Max balance dates (wallet balance)", f"{wallet_stats['low_date']} / {wallet_stats['high_date']}", ), ] ) if "max_drawdown_abs" in wallet_stats: # Assume that if sharpe is there, all others are there as well. - drawdown_metrics.extend( - [ - ( - "Absolute drawdown (unrealized)", - f"{fmt_coin(wallet_stats['max_drawdown_abs'], stake)} " - f"({wallet_stats['max_drawdown_account']:.2%})", - ), - ] + drawdown_metrics.insert( + 2, + ( + "Absolute drawdown (wallet balance)", + f"{fmt_coin(wallet_stats['max_drawdown_abs'], stake)} " + f"({wallet_stats['max_drawdown_account']:.2%})", + ), ) # Newly added fields should be ignored if they are missing in strat_results. hyperopt-show @@ -348,23 +347,32 @@ def text_table_add_metrics(strat_results: dict) -> None: ), ("Total profit %", f"{strat_results['profit_total']:.2%}"), ("CAGR %", f"{strat_results['cagr']:.2%}" if "cagr" in strat_results else "N/A"), - ("Sharpe", f"{strat_results['sharpe']:.2f}" if "sharpe" in strat_results else "N/A"), ( - "Sharpe (unrealized)", + "Sharpe (closed trades)", + f"{strat_results['sharpe']:.2f}" if "sharpe" in strat_results else "N/A", + ), + ( + "Sharpe (daily wallet balance)", f"{wallet_stats['sharpe']:.2f}" if wallet_stats and "sharpe" in wallet_stats else "N/A", ), - ("Sortino", f"{strat_results['sortino']:.2f}" if "sortino" in strat_results else "N/A"), ( - "Sortino (unrealized)", + "Sortino (closed trades)", + f"{strat_results['sortino']:.2f}" if "sortino" in strat_results else "N/A", + ), + ( + "Sortino (daily wallet balance)", f"{wallet_stats['sortino']:.2f}" if wallet_stats and "sortino" in wallet_stats else "N/A", ), - ("Calmar", f"{strat_results['calmar']:.2f}" if "calmar" in strat_results else "N/A"), ( - "Calmar (unrealized)", + "Calmar (closed trades)", + f"{strat_results['calmar']:.2f}" if "calmar" in strat_results else "N/A", + ), + ( + "Calmar (daily wallet balance)", f"{wallet_stats['calmar']:.2f}" if wallet_stats and "calmar" in wallet_stats else "N/A", From 76c09299a9bdf2c529073f4c8a573f1711ef05ea Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 09:18:37 +0200 Subject: [PATCH 208/315] feat: calculate complete drawdown metrics from wallet (incl. underwater) --- .../optimize_reports/optimize_reports.py | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index a17d92b59..1648db860 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -68,9 +68,16 @@ def generate_wallet_stats(wallet_df: DataFrame, stake_currency: str) -> dict[str calmar = calculate_calmar_from_balance(wallet) try: drawdown = calculate_max_drawdown_from_balance(wallet) + # max_relative_drawdown = Underwater + drawdown_duration = drawdown.low_date - drawdown.high_date + except ValueError: drawdown = None - + drawdown_duration = timedelta() + try: + underwater = calculate_max_drawdown_from_balance(wallet, relative=True) + except ValueError: + underwater = None return { "start_balance": start_balance, "end_balance": end_balance, @@ -79,7 +86,13 @@ def generate_wallet_stats(wallet_df: DataFrame, stake_currency: str) -> dict[str "sharpe": sharpe, "sortino": sortino, "calmar": calmar, + "low_date": low_date.strftime(DATETIME_PRINT_FORMAT), + "low_ts": int(low_date.timestamp() * 1000), + "high_date": high_date.strftime(DATETIME_PRINT_FORMAT), + "high_ts": int(high_date.timestamp() * 1000), + # Drawdown metrics "max_drawdown_account": drawdown.relative_account_drawdown if drawdown else 0.0, + "max_relative_drawdown": underwater.relative_account_drawdown, "max_drawdown_abs": drawdown.drawdown_abs if drawdown else 0.0, "drawdown_start": ( drawdown.high_date.strftime(DATETIME_PRINT_FORMAT) @@ -101,10 +114,10 @@ def generate_wallet_stats(wallet_df: DataFrame, stake_currency: str) -> dict[str if drawdown and drawdown.low_date is not None else None ), - "low_date": low_date.strftime(DATETIME_PRINT_FORMAT), - "low_ts": int(low_date.timestamp() * 1000), - "high_date": high_date.strftime(DATETIME_PRINT_FORMAT), - "high_ts": int(high_date.timestamp() * 1000), + "drawdown_duration": drawdown_duration, + "drawdown_duration_s": drawdown_duration.total_seconds(), + "max_drawdown_low": drawdown.low_value, + "max_drawdown_high": drawdown.high_value, } From a28545a67a7702502606ba72e2ac627d882c1854 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 09:18:46 +0200 Subject: [PATCH 209/315] feat: improved backtst output --- .../optimize/optimize_reports/bt_output.py | 49 ++++++++++++++----- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index 4f8143420..5834d890d 100644 --- a/freqtrade/optimize/optimize_reports/bt_output.py +++ b/freqtrade/optimize/optimize_reports/bt_output.py @@ -9,6 +9,8 @@ from freqtrade.util import decimals_per_coin, fmt_coin, print_rich_table logger = logging.getLogger(__name__) +__EMPTY_LINE = ("", "") + def _get_line_floatfmt(stake_currency: str) -> list[str]: """ @@ -201,7 +203,7 @@ def text_table_add_metrics(strat_results: dict) -> None: short_metrics = ( [ - ("", ""), # Empty line to improve readability + __EMPTY_LINE, # Empty line to improve readability ( "Long / Short trades", f"{strat_results.get('trade_count_long', 'total_trades')} / " @@ -311,13 +313,35 @@ def text_table_add_metrics(strat_results: dict) -> None: ) if "max_drawdown_abs" in wallet_stats: # Assume that if sharpe is there, all others are there as well. - drawdown_metrics.insert( - 2, - ( - "Absolute drawdown (wallet balance)", - f"{fmt_coin(wallet_stats['max_drawdown_abs'], stake)} " - f"({wallet_stats['max_drawdown_account']:.2%})", - ), + drawdown_metrics.extend( + [ + __EMPTY_LINE, # Empty line to improve readability + ( + "Max % of account underwater (balance)", + f"{wallet_stats['max_relative_drawdown']:.2%}", + ), + ( + "Absolute drawdown (wallet balance)", + f"{fmt_coin(wallet_stats['max_drawdown_abs'], stake)} " + f"({wallet_stats['max_drawdown_account']:.2%})", + ), + ( + "Drawdown duration", + wallet_stats["drawdown_duration"] + if "drawdown_duration" in wallet_stats + else "N/A", + ), + ( + "Profit at drawdown start", + fmt_coin(wallet_stats["max_drawdown_high"], stake), + ), + ( + "Profit at drawdown end", + fmt_coin(wallet_stats["max_drawdown_low"], stake), + ), + ("Drawdown start", wallet_stats["drawdown_start"]), + ("Drawdown end", wallet_stats["drawdown_end"]), + ] ) # Newly added fields should be ignored if they are missing in strat_results. hyperopt-show @@ -328,7 +352,7 @@ def text_table_add_metrics(strat_results: dict) -> None: ("Backtesting to", strat_results["backtest_end"]), *trading_mode, ("Max open trades", strat_results["max_open_trades"]), - ("", ""), # Empty line to improve readability + __EMPTY_LINE, # Empty line to improve readability ( "Total/Daily Avg Trades", f"{strat_results['total_trades']} / {strat_results['trades_per_day']}", @@ -405,12 +429,13 @@ def text_table_add_metrics(strat_results: dict) -> None: "Avg. stake amount", fmt_coin(strat_results["avg_stake_amount"], stake), ), + ("Market change", f"{strat_results['market_change']:.2%}"), ( "Total trade volume", fmt_coin(strat_results["total_volume"], stake), ), *short_metrics, - ("", ""), # Empty line to improve readability + __EMPTY_LINE, # Empty line to improve readability ( "Best Pair", f"{strat_results['best_pair']['key']} " @@ -466,10 +491,10 @@ def text_table_add_metrics(strat_results: dict) -> None: f"{strat_results.get('timedout_exit_orders', 'N/A')}", ), *entry_adjustment_metrics, - ("", ""), # Empty line to improve readability + __EMPTY_LINE, # Empty line to improve readability *wallet_metrics, + __EMPTY_LINE, # Empty line to improve readability *drawdown_metrics, - ("Market change", f"{strat_results['market_change']:.2%}"), ] print_rich_table(metrics, ["Metric", "Value"], summary="SUMMARY METRICS", justify="left") From 44919adae461707ed4dbdec2fc279af9440f78ed Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 09:27:47 +0200 Subject: [PATCH 210/315] feat: improve backtest-output --- .../optimize/optimize_reports/bt_output.py | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index 5834d890d..dbb3eff82 100644 --- a/freqtrade/optimize/optimize_reports/bt_output.py +++ b/freqtrade/optimize/optimize_reports/bt_output.py @@ -1,6 +1,8 @@ import logging from typing import Any, Literal +from rich.text import Text + from freqtrade.constants import UNLIMITED_STAKE_AMOUNT, Config from freqtrade.ft_types import BacktestResultType from freqtrade.optimize.optimize_reports.optimize_reports import generate_periodic_breakdown_stats @@ -316,6 +318,7 @@ def text_table_add_metrics(strat_results: dict) -> None: drawdown_metrics.extend( [ __EMPTY_LINE, # Empty line to improve readability + (Text("Wallet based Metrics", style="bold"), ""), ( "Max % of account underwater (balance)", f"{wallet_stats['max_relative_drawdown']:.2%}", @@ -341,6 +344,24 @@ def text_table_add_metrics(strat_results: dict) -> None: ), ("Drawdown start", wallet_stats["drawdown_start"]), ("Drawdown end", wallet_stats["drawdown_end"]), + ( + "Sharpe (daily wallet balance)", + f"{wallet_stats['sharpe']:.2f}" + if wallet_stats and "sharpe" in wallet_stats + else "N/A", + ), + ( + "Sortino (daily wallet balance)", + f"{wallet_stats['sortino']:.2f}" + if wallet_stats and "sortino" in wallet_stats + else "N/A", + ), + ( + "Calmar (daily wallet balance)", + f"{wallet_stats['calmar']:.2f}" + if wallet_stats and "calmar" in wallet_stats + else "N/A", + ), ] ) @@ -375,32 +396,14 @@ def text_table_add_metrics(strat_results: dict) -> None: "Sharpe (closed trades)", f"{strat_results['sharpe']:.2f}" if "sharpe" in strat_results else "N/A", ), - ( - "Sharpe (daily wallet balance)", - f"{wallet_stats['sharpe']:.2f}" - if wallet_stats and "sharpe" in wallet_stats - else "N/A", - ), ( "Sortino (closed trades)", f"{strat_results['sortino']:.2f}" if "sortino" in strat_results else "N/A", ), - ( - "Sortino (daily wallet balance)", - f"{wallet_stats['sortino']:.2f}" - if wallet_stats and "sortino" in wallet_stats - else "N/A", - ), ( "Calmar (closed trades)", f"{strat_results['calmar']:.2f}" if "calmar" in strat_results else "N/A", ), - ( - "Calmar (daily wallet balance)", - f"{wallet_stats['calmar']:.2f}" - if wallet_stats and "calmar" in wallet_stats - else "N/A", - ), ("SQN", f"{strat_results['sqn']:.2f}" if "sqn" in strat_results else "N/A"), ( "Profit factor", From 2d930f1fff27b711cf14db391fa26a7ddb4bdf72 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 09:33:39 +0200 Subject: [PATCH 211/315] chore: improved wallet stat drawdown safety --- freqtrade/optimize/optimize_reports/optimize_reports.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index 1648db860..a5d38b850 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -92,7 +92,7 @@ def generate_wallet_stats(wallet_df: DataFrame, stake_currency: str) -> dict[str "high_ts": int(high_date.timestamp() * 1000), # Drawdown metrics "max_drawdown_account": drawdown.relative_account_drawdown if drawdown else 0.0, - "max_relative_drawdown": underwater.relative_account_drawdown, + "max_relative_drawdown": underwater.relative_account_drawdown if underwater else 0.0, "max_drawdown_abs": drawdown.drawdown_abs if drawdown else 0.0, "drawdown_start": ( drawdown.high_date.strftime(DATETIME_PRINT_FORMAT) @@ -116,8 +116,8 @@ def generate_wallet_stats(wallet_df: DataFrame, stake_currency: str) -> dict[str ), "drawdown_duration": drawdown_duration, "drawdown_duration_s": drawdown_duration.total_seconds(), - "max_drawdown_low": drawdown.low_value, - "max_drawdown_high": drawdown.high_value, + "max_drawdown_low": drawdown.low_value if drawdown else 0.0, + "max_drawdown_high": drawdown.high_value if drawdown else 0.0, } From 22707d6c042f1c3fc8c288f0a6f33971f7d886f3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 09:51:54 +0200 Subject: [PATCH 212/315] test: update test with new metrics --- tests/optimize/test_optimize_reports.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 2d82be360..c54dea689 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -658,16 +658,21 @@ def test_text_table_add_metrics_shows_wallet_ratios(testdatadir, capsys): "sortino": 2.34, "calmar": 3.45, "max_drawdown_account": 0.12, + "max_relative_drawdown": 0.15, "max_drawdown_abs": 0.05, + "drawdown_start": "2025-01-01 12:00:00", + "drawdown_end": "2025-01-01 18:00:00", + "max_drawdown_high": 1.12, + "max_drawdown_low": 0.95, } text_table_add_metrics(strat_results) text = capsys.readouterr().out - assert "Sharpe ratio balance" in text - assert "Sortino ratio balance" in text - assert "Calmar ratio balance" in text - assert "Max drawdown balance" in text + assert "Sharpe (daily wallet balance)" in text + assert "Sortino (daily wallet balance)" in text + assert "Calmar (daily wallet balance)" in text + assert "Max % of account underwater (balance)" in text def test_generate_periodic_breakdown_stats(testdatadir): From da9f592d3dd06bce867f910d4730934757874129 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 10:00:16 +0200 Subject: [PATCH 213/315] docs: update backtesting docs --- docs/backtesting.md | 352 ++++++++++++++++++++++++-------------------- 1 file changed, 189 insertions(+), 163 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index eaf3d1581..5c758880b 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -160,118 +160,131 @@ The most important in the backtesting is to understand the result. A backtesting result will look like that: ``` - BACKTESTING REPORT -┏━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ -┃ Pair ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDT ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ -┡━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩ -│ LTC/USDT:USDT │ 16 │ 1.0 │ 56.176 │ 5.62 │ 16:16:00 │ 16 0 0 100 │ -│ ETC/USDT:USDT │ 12 │ 0.72 │ 30.936 │ 3.09 │ 9:55:00 │ 11 0 1 91.7 │ -│ ETH/USDT:USDT │ 8 │ 0.66 │ 17.864 │ 1.79 │ 1 day, 13:55:00 │ 7 0 1 87.5 │ -│ XLM/USDT:USDT │ 10 │ 0.31 │ 11.054 │ 1.11 │ 12:08:00 │ 9 0 1 90.0 │ -│ BTC/USDT:USDT │ 8 │ 0.21 │ 7.289 │ 0.73 │ 3 days, 1:24:00 │ 6 0 2 75.0 │ -│ XRP/USDT:USDT │ 9 │ -0.14 │ -7.261 │ -0.73 │ 21:18:00 │ 8 0 1 88.9 │ -│ DOT/USDT:USDT │ 6 │ -0.4 │ -9.187 │ -0.92 │ 5:35:00 │ 4 0 2 66.7 │ -│ ADA/USDT:USDT │ 8 │ -1.76 │ -52.098 │ -5.21 │ 11:38:00 │ 6 0 2 75.0 │ -│ TOTAL │ 77 │ 0.22 │ 54.774 │ 5.48 │ 22:12:00 │ 67 0 10 87.0 │ -└───────────────┴────────┴──────────────┴─────────────────┴──────────────┴─────────────────┴────────────────────────┘ - LEFT OPEN TRADES REPORT -┏━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ -┃ Pair ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDT ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ -┡━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩ -│ BTC/USDT:USDT │ 1 │ -4.14 │ -9.930 │ -0.99 │ 17 days, 8:00:00 │ 0 0 1 0 │ -│ ETC/USDT:USDT │ 1 │ -4.24 │ -15.365 │ -1.54 │ 10:40:00 │ 0 0 1 0 │ -│ DOT/USDT:USDT │ 1 │ -5.29 │ -19.125 │ -1.91 │ 11:30:00 │ 0 0 1 0 │ -│ TOTAL │ 3 │ -4.56 │ -44.420 │ -4.44 │ 6 days, 2:03:00 │ 0 0 3 0 │ -└───────────────┴────────┴──────────────┴─────────────────┴──────────────┴──────────────────┴────────────────────────┘ - ENTER TAG STATS -┏━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ -┃ Enter Tag ┃ Entries ┃ Avg Profit % ┃ Tot Profit USDT ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ -┡━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩ -│ OTHER │ 77 │ 0.22 │ 54.774 │ 5.48 │ 22:12:00 │ 67 0 10 87.0 │ -│ TOTAL │ 77 │ 0.22 │ 54.774 │ 5.48 │ 22:12:00 │ 67 0 10 87.0 │ -└───────────┴─────────┴──────────────┴─────────────────┴──────────────┴──────────────┴────────────────────────┘ - EXIT REASON STATS -┏━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ -┃ Exit Reason ┃ Exits ┃ Avg Profit % ┃ Tot Profit USDT ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ -┡━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩ -│ roi │ 67 │ 1.05 │ 242.179 │ 24.22 │ 15:49:00 │ 67 0 0 100 │ -│ exit_signal │ 4 │ -2.23 │ -31.217 │ -3.12 │ 1 day, 8:38:00 │ 0 0 4 0 │ -│ force_exit │ 3 │ -4.56 │ -44.420 │ -4.44 │ 6 days, 2:03:00 │ 0 0 3 0 │ -│ stop_loss │ 3 │ -10.14 │ -111.768 │ -11.18 │ 1 day, 3:05:00 │ 0 0 3 0 │ -│ TOTAL │ 77 │ 0.22 │ 54.774 │ 5.48 │ 22:12:00 │ 67 0 10 87.0 │ -└─────────────┴───────┴──────────────┴─────────────────┴──────────────┴─────────────────┴────────────────────────┘ - MIXED TAG STATS -┏━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ -┃ Enter Tag ┃ Exit Reason ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDT ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ -┡━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩ -│ │ roi │ 67 │ 1.05 │ 242.179 │ 24.22 │ 15:49:00 │ 67 0 0 100 │ -│ │ exit_signal │ 4 │ -2.23 │ -31.217 │ -3.12 │ 1 day, 8:38:00 │ 0 0 4 0 │ -│ │ force_exit │ 3 │ -4.56 │ -44.420 │ -4.44 │ 6 days, 2:03:00 │ 0 0 3 0 │ -│ │ stop_loss │ 3 │ -10.14 │ -111.768 │ -11.18 │ 1 day, 3:05:00 │ 0 0 3 0 │ -│ TOTAL │ │ 77 │ 0.22 │ 54.774 │ 5.48 │ 22:12:00 │ 67 0 10 87.0 │ -└───────────┴─────────────┴────────┴──────────────┴─────────────────┴──────────────┴─────────────────┴────────────────────────┘ - SUMMARY METRICS -┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -┃ Metric ┃ Value ┃ -┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ -│ Backtesting from │ 2025-07-01 00:00:00 │ -│ Backtesting to │ 2025-08-01 00:00:00 │ -│ Trading Mode │ Isolated Futures │ -│ Max open trades │ 3 │ -│ │ │ -│ Total/Daily Avg Trades │ 77 / 2.48 │ -│ Starting balance │ 1000 USDT │ -│ Final balance │ 1054.669 USDT │ -│ Absolute profit │ 54.669 USDT │ -│ Total profit % │ 5.47% │ -│ CAGR % │ 87.14% │ -│ Sortino │ 2.46 │ -│ Sharpe │ 3.73 │ -│ Calmar │ 40.81 │ -│ SQN │ 0.69 │ -│ Profit factor │ 1.29 │ -│ Expectancy (Ratio) │ 0.71 (0.04) │ -│ Avg. daily profit │ 1.764 USDT │ -│ Avg. stake amount │ 345.251 USDT │ -│ Total trade volume │ 53352.96 USDT │ -│ │ │ -│ Long / Short trades │ 67 / 10 │ -│ Long / Short profit % │ 8.93% / -3.46% │ -│ Long / Short profit USDT │ 89.262 / -34.593 │ -│ │ │ -│ Best Pair │ LTC/USDT:USDT 5.62% │ -│ Worst Pair │ ADA/USDT:USDT -5.21% │ -│ Best trade │ ETC/USDT:USDT 2.00% │ -│ Worst trade │ ADA/USDT:USDT -10.17% │ -│ Best day │ 26.931 USDT │ -│ Worst day │ -47.741 USDT │ -│ Days win/draw/lose │ 20 / 6 / 5 │ -│ Min/Max/Avg. Duration Winners │ 0d 00:35 / 5d 18:15 / 0d 15:49 │ -│ Min/Max/Avg. Duration Losers │ 0d 10:40 / 17d 08:00 / 2d 17:00 │ -│ Max Consecutive Wins / Loss │ 36 / 3 │ -│ Rejected Entry signals │ 258 │ -│ Entry/Exit Timeouts │ 0 / 0 │ -│ │ │ -│ Min/Max balance realized │ 1003.168 USDT / 1149.577 USDT │ -│ Min/Max balance unrealized │ 1000 USDT / 1149.577 USDT │ -│ Min/Max balance dates │ 2025-07-01 00:05:00 / 2025-07-22 15:15:00 │ -│ Max % of account underwater │ 8.26% │ -│ Absolute drawdown │ 94.908 USDT (8.26%) │ -│ Drawdown duration │ 9 days 08:50:00 │ -│ Profit at drawdown start │ 149.577 USDT │ -│ Profit at drawdown end │ 54.669 USDT │ -│ Drawdown start │ 2025-07-22 15:10:00 │ -│ Drawdown end │ 2025-08-01 00:00:00 │ -│ Market change │ 30.51% │ -└───────────────────────────────┴───────────────────────────────────────────┘ + BACKTESTING REPORT +┏━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Pair ┃ Trades ┃ Avg Profit % ┃ Tot Profit ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ +┡━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ LTC/USDT:USDT │ 16 │ 1.01 │ 56.882 │ 5.69 │ 16:16:00 │ 16 0 0 100 │ +│ ETC/USDT:USDT │ 12 │ 0.73 │ 31.513 │ 3.15 │ 9:55:00 │ 11 0 1 91.7 │ +│ ETH/USDT:USDT │ 8 │ 0.69 │ 18.659 │ 1.87 │ 1 day, 13:55:00 │ 7 0 1 87.5 │ +│ XLM/USDT:USDT │ 10 │ 0.3 │ 10.694 │ 1.07 │ 12:08:00 │ 9 0 1 90.0 │ +│ BTC/USDT:USDT │ 8 │ 0.22 │ 7.502 │ 0.75 │ 3 days, 1:24:00 │ 6 0 2 75.0 │ +│ XRP/USDT:USDT │ 9 │ -0.13 │ -6.837 │ -0.68 │ 21:18:00 │ 8 0 1 88.9 │ +│ DOT/USDT:USDT │ 6 │ -0.39 │ -9.169 │ -0.92 │ 5:35:00 │ 4 0 2 66.7 │ +│ ADA/USDT:USDT │ 8 │ -1.75 │ -52.089 │ -5.21 │ 11:38:00 │ 6 0 2 75.0 │ +│ TOTAL │ 77 │ 0.23 │ 57.157 │ 5.72 │ 22:12:00 │ 67 0 10 87.0 │ +└───────────────┴────────┴──────────────┴─────────────┴──────────────┴─────────────────┴────────────────────────┘ + LEFT OPEN TRADES REPORT +┏━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Pair ┃ Trades ┃ Avg Profit % ┃ Tot Profit ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ +┡━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ BTC/USDT:USDT │ 1 │ -4.14 │ -9.930 │ -0.99 │ 17 days, 8:00:00 │ 0 0 1 0 │ +│ ETC/USDT:USDT │ 1 │ -4.24 │ -15.365 │ -1.54 │ 10:40:00 │ 0 0 1 0 │ +│ DOT/USDT:USDT │ 1 │ -5.29 │ -19.166 │ -1.92 │ 11:30:00 │ 0 0 1 0 │ +│ TOTAL │ 3 │ -4.56 │ -44.461 │ -4.45 │ 6 days, 2:03:00 │ 0 0 3 0 │ +└───────────────┴────────┴──────────────┴─────────────┴──────────────┴──────────────────┴────────────────────────┘ + ENTER TAG STATS +┏━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Enter Tag ┃ Entries ┃ Avg Profit % ┃ Tot Profit ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ +┡━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ OTHER │ 77 │ 0.23 │ 57.157 │ 5.72 │ 22:12:00 │ 67 0 10 87.0 │ +│ TOTAL │ 77 │ 0.23 │ 57.157 │ 5.72 │ 22:12:00 │ 67 0 10 87.0 │ +└───────────┴─────────┴──────────────┴─────────────┴──────────────┴──────────────┴────────────────────────┘ + EXIT REASON STATS +┏━━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Exit Reason ┃ Exits ┃ Avg Profit % ┃ Tot Profit ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ +┡━━━━━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ roi │ 67 │ 1.06 │ 245.117 │ 24.51 │ 15:49:00 │ 67 0 0 100 │ +│ exit_signal │ 4 │ -2.23 │ -31.226 │ -3.12 │ 1 day, 8:38:00 │ 0 0 4 0 │ +│ force_exit │ 3 │ -4.56 │ -44.461 │ -4.45 │ 6 days, 2:03:00 │ 0 0 3 0 │ +│ stop_loss │ 3 │ -10.14 │ -112.273 │ -11.23 │ 1 day, 3:05:00 │ 0 0 3 0 │ +│ TOTAL │ 77 │ 0.23 │ 57.157 │ 5.72 │ 22:12:00 │ 67 0 10 87.0 │ +└─────────────┴───────┴──────────────┴─────────────┴──────────────┴─────────────────┴────────────────────────┘ + MIXED TAG STATS +┏━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Enter Tag ┃ Exit Reason ┃ Trades ┃ Avg Profit % ┃ Tot Profit ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ +┡━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ │ roi │ 67 │ 1.06 │ 245.117 │ 24.51 │ 15:49:00 │ 67 0 0 100 │ +│ │ exit_signal │ 4 │ -2.23 │ -31.226 │ -3.12 │ 1 day, 8:38:00 │ 0 0 4 0 │ +│ │ force_exit │ 3 │ -4.56 │ -44.461 │ -4.45 │ 6 days, 2:03:00 │ 0 0 3 0 │ +│ │ stop_loss │ 3 │ -10.14 │ -112.273 │ -11.23 │ 1 day, 3:05:00 │ 0 0 3 0 │ +│ TOTAL │ │ 77 │ 0.23 │ 57.157 │ 5.72 │ 22:12:00 │ 67 0 10 87.0 │ +└───────────┴─────────────┴────────┴──────────────┴─────────────┴──────────────┴─────────────────┴────────────────────────┘ + SUMMARY METRICS +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Metric ┃ Value ┃ +┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ Backtesting from │ 2025-07-01 00:00:00 │ +│ Backtesting to │ 2025-08-01 00:00:00 │ +│ Trading Mode │ Isolated Futures │ +│ Max open trades │ 3 │ +│ │ │ +│ Total/Daily Avg Trades │ 77 / 2.48 │ +│ Starting balance │ 1000 USDT │ +│ Final balance │ 1057.157 USDT │ +│ Absolute profit │ 57.157 USDT │ +│ Total profit % │ 5.72% │ +│ CAGR % │ 92.41% │ +│ Sharpe (closed trades) │ 3.89 │ +│ Sortino (closed trades) │ 2.57 │ +│ Calmar (closed trades) │ 43.03 │ +│ SQN │ 0.71 │ +│ Profit factor │ 1.30 │ +│ Expectancy (Ratio) │ 0.74 (0.04) │ +│ Avg. daily profit │ 1.844 USDT │ +│ Avg. stake amount │ 345.478 USDT │ +│ Market change │ 30.51% │ +│ Total trade volume │ 53390.788 USDT │ +│ │ │ +│ Long / Short trades │ 67 / 10 │ +│ Long / Short profit % │ 9.19% / -3.48% │ +│ Long / Short profit USDT │ 91.940 / -34.783 │ +│ │ │ +│ Best Pair │ LTC/USDT:USDT 5.69% │ +│ Worst Pair │ ADA/USDT:USDT -5.21% │ +│ Best trade │ XRP/USDT:USDT 2.00% │ +│ Worst trade │ ADA/USDT:USDT -10.17% │ +│ Best day │ 27.031 USDT │ +│ Worst day │ -47.826 USDT │ +│ Days win/draw/lose │ 20 / 6 / 5 │ +│ Min/Max/Avg. Duration Winners │ 0d 00:35 / 5d 18:15 / 0d 15:49 │ +│ Min/Max/Avg. Duration Losers │ 0d 10:40 / 17d 08:00 / 2d 17:00 │ +│ Max Consecutive Wins / Loss │ 36 / 3 │ +│ Rejected Entry signals │ 258 │ +│ Entry/Exit Timeouts │ 0 / 0 │ +│ │ │ +│ Min/Max balance (closed trades) │ 1003.205 USDT / 1151.425 USDT │ +│ Max % of account underwater │ 8.19% │ +│ Absolute drawdown │ 94.268 USDT (8.19%) │ +│ Drawdown duration │ 9 days 08:50:00 │ +│ Profit at drawdown start │ 151.425 USDT │ +│ Profit at drawdown end │ 57.157 USDT │ +│ Drawdown start │ 2025-07-22 15:10:00 │ +│ Drawdown end │ 2025-08-01 00:00:00 │ +│ │ │ +│ Wallet based Metrics │ │ +│ Min/Max balance (wallet balance) │ 1000 USDT / 1151.425 USDT │ +│ Min/Max balance dates (wallet balance) │ 2025-07-01 00:05:00 / 2025-07-22 15:15:00 │ +│ Max % of account underwater (balance) │ 5.01% │ +│ Absolute drawdown (wallet balance) │ 54.76 USDT (4.76%) │ +│ Drawdown duration │ 7 days 20:35:00 │ +│ Profit at drawdown start │ 151.425 USDT │ +│ Profit at drawdown end │ 96.664 USDT │ +│ Drawdown start │ 2025-07-22 15:15:00 │ +│ Drawdown end │ 2025-07-30 11:50:00 │ +│ Sharpe (daily wallet balance) │ 4.42 │ +│ Sortino (daily wallet balance) │ 4.35 │ +│ Calmar (daily wallet balance) │ 136.07 │ +└────────────────────────────────────────┴───────────────────────────────────────────┘ Backtested 2025-07-01 00:00:00 -> 2025-08-01 00:00:00 | Max open trades : 3 - STRATEGY SUMMARY -┏━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓ -┃ Strategy ┃ Trades ┃ Avg Profit % ┃ Tot Profit USDT ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ Drawdown ┃ -┡━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩ -│ SampleStrategy │ 77 │ 0.22 │ 54.774 │ 5.48 │ 22:12:00 │ 67 0 10 87.0 │ 94.647 USDT 8.23% │ -└────────────────┴────────┴──────────────┴─────────────────┴──────────────┴──────────────┴────────────────────────┴────────────────────┘ + STRATEGY SUMMARY +┏━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓ +┃ Strategy ┃ Trades ┃ Avg Profit % ┃ Tot Profit ┃ Tot Profit % ┃ Avg Duration ┃ Win Draw Loss Win% ┃ Drawdown ┃ +┡━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩ +│ SampleStrategy │ 77 │ 0.23 │ 57.157 │ 5.72 │ 22:12:00 │ 67 0 10 87.0 │ 94.268 8.19% │ +└────────────────┴────────┴──────────────┴─────────────┴──────────────┴──────────────┴────────────────────────┴────────────────┘ + ``` ### Backtesting report table @@ -330,59 +343,72 @@ The last element of the backtest report is the summary metrics table. It contains key metrics about the performance of your strategy on backtesting data. ``` -┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -┃ Metric ┃ Value ┃ -┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ -│ Backtesting from │ 2025-07-01 00:00:00 │ -│ Backtesting to │ 2025-08-01 00:00:00 │ -│ Trading Mode │ Isolated Futures │ -│ Max open trades │ 3 │ -│ │ │ -│ Total/Daily Avg Trades │ 77 / 2.48 │ -│ Starting balance │ 1000 USDT │ -│ Final balance │ 1054.669 USDT │ -│ Absolute profit │ 54.669 USDT │ -│ Total profit % │ 5.47% │ -│ CAGR % │ 87.14% │ -│ Sortino │ 2.46 │ -│ Sharpe │ 3.73 │ -│ Calmar │ 40.81 │ -│ SQN │ 0.69 │ -│ Profit factor │ 1.29 │ -│ Expectancy (Ratio) │ 0.71 (0.04) │ -│ Avg. daily profit │ 1.764 USDT │ -│ Avg. stake amount │ 345.251 USDT │ -│ Total trade volume │ 53352.96 USDT │ -│ │ │ -│ Long / Short trades │ 67 / 10 │ -│ Long / Short profit % │ 8.93% / -3.46% │ -│ Long / Short profit USDT │ 89.262 / -34.593 │ -│ │ │ -│ Best Pair │ LTC/USDT:USDT 5.62% │ -│ Worst Pair │ ADA/USDT:USDT -5.21% │ -│ Best trade │ ETC/USDT:USDT 2.00% │ -│ Worst trade │ ADA/USDT:USDT -10.17% │ -│ Best day │ 26.931 USDT │ -│ Worst day │ -47.741 USDT │ -│ Days win/draw/lose │ 20 / 6 / 5 │ -│ Min/Max/Avg. Duration Winners │ 0d 00:35 / 5d 18:15 / 0d 15:49 │ -│ Min/Max/Avg. Duration Losers │ 0d 10:40 / 17d 08:00 / 2d 17:00 │ -│ Max Consecutive Wins / Loss │ 36 / 3 │ -│ Rejected Entry signals │ 258 │ -│ Entry/Exit Timeouts │ 0 / 0 │ -│ │ │ -│ Min/Max balance realized │ 1003.168 USDT / 1149.577 USDT │ -│ Min/Max balance unrealized │ 1000 USDT / 1149.577 USDT │ -│ Min/Max balance dates │ 2025-07-01 00:05:00 / 2025-07-22 15:15:00 │ -│ Max % of account underwater │ 8.26% │ -│ Absolute drawdown │ 94.908 USDT (8.26%) │ -│ Drawdown duration │ 9 days 08:50:00 │ -│ Profit at drawdown start │ 149.577 USDT │ -│ Profit at drawdown end │ 54.669 USDT │ -│ Drawdown start │ 2025-07-22 15:10:00 │ -│ Drawdown end │ 2025-08-01 00:00:00 │ -│ Market change │ 30.51% │ -└───────────────────────────────┴───────────────────────────────────────────┘ + SUMMARY METRICS +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ Metric ┃ Value ┃ +┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩ +│ Backtesting from │ 2025-07-01 00:00:00 │ +│ Backtesting to │ 2025-08-01 00:00:00 │ +│ Trading Mode │ Isolated Futures │ +│ Max open trades │ 3 │ +│ │ │ +│ Total/Daily Avg Trades │ 77 / 2.48 │ +│ Starting balance │ 1000 USDT │ +│ Final balance │ 1057.157 USDT │ +│ Absolute profit │ 57.157 USDT │ +│ Total profit % │ 5.72% │ +│ CAGR % │ 92.41% │ +│ Sharpe (closed trades) │ 3.89 │ +│ Sortino (closed trades) │ 2.57 │ +│ Calmar (closed trades) │ 43.03 │ +│ SQN │ 0.71 │ +│ Profit factor │ 1.30 │ +│ Expectancy (Ratio) │ 0.74 (0.04) │ +│ Avg. daily profit │ 1.844 USDT │ +│ Avg. stake amount │ 345.478 USDT │ +│ Market change │ 30.51% │ +│ Total trade volume │ 53390.788 USDT │ +│ │ │ +│ Long / Short trades │ 67 / 10 │ +│ Long / Short profit % │ 9.19% / -3.48% │ +│ Long / Short profit USDT │ 91.940 / -34.783 │ +│ │ │ +│ Best Pair │ LTC/USDT:USDT 5.69% │ +│ Worst Pair │ ADA/USDT:USDT -5.21% │ +│ Best trade │ XRP/USDT:USDT 2.00% │ +│ Worst trade │ ADA/USDT:USDT -10.17% │ +│ Best day │ 27.031 USDT │ +│ Worst day │ -47.826 USDT │ +│ Days win/draw/lose │ 20 / 6 / 5 │ +│ Min/Max/Avg. Duration Winners │ 0d 00:35 / 5d 18:15 / 0d 15:49 │ +│ Min/Max/Avg. Duration Losers │ 0d 10:40 / 17d 08:00 / 2d 17:00 │ +│ Max Consecutive Wins / Loss │ 36 / 3 │ +│ Rejected Entry signals │ 258 │ +│ Entry/Exit Timeouts │ 0 / 0 │ +│ │ │ +│ Min/Max balance (closed trades) │ 1003.205 USDT / 1151.425 USDT │ +│ Max % of account underwater │ 8.19% │ +│ Absolute drawdown │ 94.268 USDT (8.19%) │ +│ Drawdown duration │ 9 days 08:50:00 │ +│ Profit at drawdown start │ 151.425 USDT │ +│ Profit at drawdown end │ 57.157 USDT │ +│ Drawdown start │ 2025-07-22 15:10:00 │ +│ Drawdown end │ 2025-08-01 00:00:00 │ +│ │ │ +│ Wallet based Metrics │ │ +│ Min/Max balance (wallet balance) │ 1000 USDT / 1151.425 USDT │ +│ Min/Max balance dates (wallet balance) │ 2025-07-01 00:05:00 / 2025-07-22 15:15:00 │ +│ Max % of account underwater (balance) │ 5.01% │ +│ Absolute drawdown (wallet balance) │ 54.76 USDT (4.76%) │ +│ Drawdown duration │ 7 days 20:35:00 │ +│ Profit at drawdown start │ 151.425 USDT │ +│ Profit at drawdown end │ 96.664 USDT │ +│ Drawdown start │ 2025-07-22 15:15:00 │ +│ Drawdown end │ 2025-07-30 11:50:00 │ +│ Sharpe (daily wallet balance) │ 4.42 │ +│ Sortino (daily wallet balance) │ 4.35 │ +│ Calmar (daily wallet balance) │ 136.07 │ +└────────────────────────────────────────┴───────────────────────────────────────────┘ ``` - `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option). From 09106ecbe09c4dc8d6fddf07209eac2e52b77a5c Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 10:06:59 +0200 Subject: [PATCH 214/315] chore: further reorder backtest output --- freqtrade/optimize/optimize_reports/bt_output.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index dbb3eff82..977d402e5 100644 --- a/freqtrade/optimize/optimize_reports/bt_output.py +++ b/freqtrade/optimize/optimize_reports/bt_output.py @@ -300,8 +300,10 @@ def text_table_add_metrics(strat_results: dict) -> None: ] wallet_stats = strat_results.get("wallet_stats", {}) if wallet_stats: - wallet_metrics.extend( + drawdown_metrics.extend( [ + __EMPTY_LINE, # Empty line to improve readability + (Text("Wallet based Metrics", style="bold"), ""), ( "Min/Max balance (wallet balance)", f"{fmt_coin(wallet_stats['low_balance'], stake)} / " @@ -317,8 +319,6 @@ def text_table_add_metrics(strat_results: dict) -> None: # Assume that if sharpe is there, all others are there as well. drawdown_metrics.extend( [ - __EMPTY_LINE, # Empty line to improve readability - (Text("Wallet based Metrics", style="bold"), ""), ( "Max % of account underwater (balance)", f"{wallet_stats['max_relative_drawdown']:.2%}", @@ -496,7 +496,6 @@ def text_table_add_metrics(strat_results: dict) -> None: *entry_adjustment_metrics, __EMPTY_LINE, # Empty line to improve readability *wallet_metrics, - __EMPTY_LINE, # Empty line to improve readability *drawdown_metrics, ] print_rich_table(metrics, ["Metric", "Value"], summary="SUMMARY METRICS", justify="left") From 45d4c5d036658c612cdcb74f654b34eba96981b3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 10:07:05 +0200 Subject: [PATCH 215/315] docs: update backtesting docs --- docs/backtesting.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index 5c758880b..00776ed66 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -421,16 +421,14 @@ It contains key metrics about the performance of your strategy on backtesting da - `Total profit %`: Total profit. Aligned to the `TOTAL` row's `Tot Profit %` from the first table. Calculated as `(End capital − Starting capital) / Starting capital`. - `CAGR %`: Compound annual growth rate. - `Sharpe (closed trades)`: Annualized Sharpe ratio including only closed trades (ignoring open trades with profits or losses). -- `Sharpe (wallet balance)` Annualized Sharpe ratio calculation but including unrealized profits. - `Sortino (closed trades)`: Annualized Sortino ratio including only closed trades (ignoring open trades with profits or losses). -- `Sortino (wallet balance)` Annualized Sortino ratio calculation but including unrealized profits. - `Calmar (closed trades)`: Annualized Calmar ratio including only closed trades (ignoring open trades with profits or losses). -- `Calmar (wallet balance)` Annualized Calmar ratio calculation but including unrealized profits. - `SQN`: System Quality Number (SQN) - by Van Tharp. - `Profit factor`: Sum of the profits of all winning trades divided by the sum of the losses of all losing trades. - `Expectancy (Ratio)`: Expectancy ratio, which is the average profit or loss per trade. A negative expectancy ratio means that your strategy is not profitable. - `Avg. daily profit`: Average profit per day, calculated as `(Total Profit / Backtest Days)`. - `Avg. stake amount`: Average stake amount, either `stake_amount` or the average when using dynamic stake amount. +- `Market change`: Change of the market during the backtest period. Calculated as the average of all pairs' changes from the first to the last candle using the "close" column. - `Total trade volume`: Volume generated on the exchange to reach the above profit. - `Long / Short trades`: Split long/short trade counts (only shown when short trades were made). - `Long / Short profit %`: Profit percentage for long and short trades (only shown when short trades were made). @@ -445,15 +443,20 @@ It contains key metrics about the performance of your strategy on backtesting da - `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/Max balance (closed trades)`: Lowest and Highest Wallet balance during the backtest period based on closed trades trades. -- `Min/Max balance (wallet balance)`: Lowest and Highest Wallet balance during the backtest period - including capital tied in open trades. -- `Min/Max balance dates (wallet balance)`: Dates when the minimum and maximum unrealized balance occurred. - `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`: Maximum absolute drawdown experienced, including percentage relative to the account calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.. - `Absolute drawdown (wallet balance)`: Maximum absolute drawdown experienced based on the unrealized balance, including percentage relative to the account calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`. - `Drawdown duration`: Duration of the largest drawdown period. - `Profit at drawdown start` / `Profit at drawdown end`: Profit at the beginning and end of the largest drawdown period. - `Drawdown start` / `Drawdown end`: Start and end datetime for the largest drawdown (can also be visualized via the `plot-dataframe` sub-command). -- `Market change`: Change of the market during the backtest period. Calculated as the average of all pairs' changes from the first to the last candle using the "close" column. +- `Min/Max balance (wallet balance)`: Lowest and Highest Wallet balance during the backtest period - including capital tied in open trades. +- `Min/Max balance dates (wallet balance)`: Dates when the minimum and maximum unrealized balance occurred. +- `Sharpe (wallet balance)` Annualized Sharpe ratio calculation including unrealized profits. +- `Sortino (wallet balance)` Annualized Sortino ratio calculation including unrealized profits. +- `Calmar (wallet balance)` Annualized Calmar ratio calculation including unrealized profits. + +!!! Tip "Wallet based Metrics" + The metrics under the "Wallet based Metrics" section are calculated based on the unrealized balance, which includes the capital tied in open trades. This provides a more comprehensive view of the strategy's performance, as it accounts for both realized and unrealized profits and losses. ### Daily / Weekly / Monthly / Yearly breakdown From c60d96922db23057bdda67f33374ed09a73b4d47 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 10:38:44 +0200 Subject: [PATCH 216/315] chore: improve type safety --- freqtrade/optimize/optimize_reports/bt_output.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py index 977d402e5..f42054c65 100644 --- a/freqtrade/optimize/optimize_reports/bt_output.py +++ b/freqtrade/optimize/optimize_reports/bt_output.py @@ -226,7 +226,7 @@ def text_table_add_metrics(strat_results: dict) -> None: else [] ) - drawdown_metrics = [] + drawdown_metrics: list[tuple[str | Text, str | Text]] = [] if "max_relative_drawdown" in strat_results: # Compatibility to show old hyperopt results drawdown_metrics.append( From 33211c8eb1aa5437c63a2ccc3ee8d77db0cc21aa Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 13:02:51 +0200 Subject: [PATCH 217/315] chore: don't use deprecated resmapling frequency --- freqtrade/optimize/optimize_reports/optimize_reports.py | 6 +++--- tests/optimize/test_optimize_reports.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py index a5d38b850..15d25a19c 100644 --- a/freqtrade/optimize/optimize_reports/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports/optimize_reports.py @@ -340,7 +340,7 @@ def generate_strategy_comparison(bt_stats: dict) -> list[dict]: def _get_resample_from_period(period: str) -> str: if period == "day": - return "1d" + return "1D" if period == "week": # Weekly defaulting to Monday. return "1W-MON" @@ -530,8 +530,8 @@ def generate_daily_stats(results: DataFrame) -> dict[str, Any]: "losing_days": 0, "daily_profit_list": [], } - daily_profit_rel = results.resample("1d", on="close_date")["profit_ratio"].sum() - daily_profit = results.resample("1d", on="close_date")["profit_abs"].sum().round(10) + daily_profit_rel = results.resample("1D", on="close_date")["profit_ratio"].sum() + daily_profit = results.resample("1D", on="close_date")["profit_abs"].sum().round(10) worst_rel = min(daily_profit_rel) best_rel = max(daily_profit_rel) worst = min(daily_profit) diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index c54dea689..fcf5abffd 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -713,7 +713,7 @@ def test_generate_periodic_breakdown_stats(testdatadir): def test__get_resample_from_period(): - assert _get_resample_from_period("day") == "1d" + assert _get_resample_from_period("day") == "1D" assert _get_resample_from_period("week") == "1W-MON" assert _get_resample_from_period("month") == "1ME" assert _get_resample_from_period("weekday") == "weekday" From f6f0180fc1f7660ddf3d305b38cf365d265a7e53 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 13:12:09 +0200 Subject: [PATCH 218/315] fix: use more stable "date to ms" method --- freqtrade/data/btanalysis/bt_fileutils.py | 4 ++-- freqtrade/data/history/datahandlers/jsondatahandler.py | 4 ++-- freqtrade/rpc/rpc.py | 4 ++-- tests/conftest.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/data/btanalysis/bt_fileutils.py b/freqtrade/data/btanalysis/bt_fileutils.py index a97d5bef3..2e6b23662 100644 --- a/freqtrade/data/btanalysis/bt_fileutils.py +++ b/freqtrade/data/btanalysis/bt_fileutils.py @@ -308,7 +308,7 @@ def get_backtest_market_change(filename: Path, include_ts: bool = True) -> pd.Da else: df = pd.read_feather(filename) if include_ts: - df.loc[:, "__date_ts"] = df.loc[:, "date"].astype(np.int64) // 1000 // 1000 + df.loc[:, "__date_ts"] = df.loc[:, "date"].dt.as_unit("ms").astype(np.int64) return df @@ -326,7 +326,7 @@ def get_backtest_wallet_change(filename: Path, strategy_name: str) -> pd.DataFra data = load_file_from_zip(filename, f"{filename.stem}_{strategy_name}_wallet.feather") df = pd.read_feather(BytesIO(data)) - df.loc[:, "__date_ts"] = df.loc[:, "date"].astype(np.int64) // 1000 // 1000 + df.loc[:, "__date_ts"] = df.loc[:, "date"].dt.as_unit("ms").astype(np.int64) return df except ValueError: pass diff --git a/freqtrade/data/history/datahandlers/jsondatahandler.py b/freqtrade/data/history/datahandlers/jsondatahandler.py index 1a33b3e2f..e2ab5c408 100644 --- a/freqtrade/data/history/datahandlers/jsondatahandler.py +++ b/freqtrade/data/history/datahandlers/jsondatahandler.py @@ -35,8 +35,8 @@ class JsonDataHandler(IDataHandler): filename = self._pair_data_filename(self._datadir, pair, timeframe, candle_type) self.create_dir_if_needed(filename) _data = data.copy() - # Convert date to int - _data["date"] = _data["date"].astype(np.int64) // 1000 // 1000 + # Convert date to int (milliseconds) + _data["date"] = _data["date"].dt.as_unit("ms").astype(np.int64) # Reset index, select only appropriate columns and save as json _data.reset_index(drop=True).loc[:, self._columns].to_json( diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 63f00a22d..82c66de0c 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -794,7 +794,7 @@ class RPC: results = read_sql("wallet_history", con=Trade.session.bind, parse_dates=["timestamp"]) results = results.rename({"timestamp": "date"}, axis=1) - results.loc[:, "__date_ts"] = results.loc[:, "date"].astype("int64") // 1000 // 1000 + results.loc[:, "__date_ts"] = results.loc[:, "date"].dt.as_unit("ms").astype("int64") # Exclude non-bot managed for now results_filtered = results.loc[results["bot_managed"]] @@ -1536,7 +1536,7 @@ class RPC: df_cols = [col for col in dataframe_columns if col in cols_set] dataframe = dataframe.loc[:, df_cols] - dataframe.loc[:, "__date_ts"] = dataframe.loc[:, "date"].astype(int64) // 1000 // 1000 + dataframe.loc[:, "__date_ts"] = dataframe.loc[:, "date"].dt.as_unit("ms").astype(int64) # Move signal close to separate column when signal for easy plotting for sig_type in signals.keys(): if sig_type in dataframe.columns: diff --git a/tests/conftest.py b/tests/conftest.py index 93d34fe18..a92144659 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -207,7 +207,7 @@ def generate_test_data( def generate_test_data_raw(timeframe: str, size: int, start: str = "2020-07-05", random_seed=42): """Generates data in the ohlcv format used by ccxt""" df = generate_test_data(timeframe, size, start, random_seed) - df["date"] = df.loc[:, "date"].astype(np.int64) // 1000 // 1000 + df["date"] = df.loc[:, "date"].dt.as_unit("ms").astype(np.int64) return list(list(x) for x in zip(*(df[x].values.tolist() for x in df.columns), strict=False)) From 79ea1ba1d58372038db53182f87a3ac3c6cdd02b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 19:33:28 +0200 Subject: [PATCH 219/315] fix: use is_string_dtype to check for object/string types --- freqtrade/freqai/data_drawer.py | 2 +- freqtrade/freqai/data_kitchen.py | 6 +++--- freqtrade/freqai/freqai_interface.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/freqai/data_drawer.py b/freqtrade/freqai/data_drawer.py index 960c822b5..9e9381937 100644 --- a/freqtrade/freqai/data_drawer.py +++ b/freqtrade/freqai/data_drawer.py @@ -361,7 +361,7 @@ class FreqaiDataDrawer: label_loc = df.columns.get_loc(label) pred_label_loc = predictions.columns.get_loc(label) df.iloc[-1, label_loc] = predictions.iloc[-1, pred_label_loc] - if df[label].dtype == object: + if pd.api.types.is_string_dtype(df[label].dtype): continue label_mean_loc = df.columns.get_loc(f"{label}_mean") label_std_loc = df.columns.get_loc(f"{label}_std") diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index df7c827f9..9f04e0ca4 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -435,7 +435,7 @@ class FreqaiDataKitchen: for label in predictions.columns: append_dict[label] = predictions[label] - if predictions[label].dtype == object: + if pd.api.types.is_string_dtype(predictions[label].dtype): continue if "labels_mean" in self.data and label in self.data["labels_mean"]: append_dict[f"{label}_mean"] = self.data["labels_mean"][label] @@ -879,7 +879,7 @@ class FreqaiDataKitchen: self.data["labels_mean"], self.data["labels_std"] = {}, {} for label in self.data_dictionary["train_labels"].columns: - if self.data_dictionary["train_labels"][label].dtype == object: + if pd.api.types.is_string_dtype(self.data_dictionary["train_labels"][label].dtype): continue f = spy.stats.norm.fit(self.data_dictionary["train_labels"][label]) self.data["labels_mean"][label], self.data["labels_std"][label] = f[0], f[1] @@ -905,7 +905,7 @@ class FreqaiDataKitchen: self.find_labels(dataframe) for key in self.label_list: - if dataframe[key].dtype == object: + if pd.api.types.is_string_dtype(dataframe[key].dtype): self.unique_classes[key] = dataframe[key].dropna().unique() if self.unique_classes: diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py index 1ba58d3e8..2e3e74400 100644 --- a/freqtrade/freqai/freqai_interface.py +++ b/freqtrade/freqai/freqai_interface.py @@ -676,7 +676,7 @@ class IFreqaiModel(ABC): self.set_start_dry_live_date(strat_df) for label in hist_preds_df.columns: - if hist_preds_df[label].dtype == object: + if pd.api.types.is_string_dtype(hist_preds_df[label].dtype): continue hist_preds_df[f"{label}_mean"] = 0 hist_preds_df[f"{label}_std"] = 0 @@ -706,7 +706,7 @@ class IFreqaiModel(ABC): num_candles = self.freqai_info.get("fit_live_predictions_candles", 100) dk.data["labels_mean"], dk.data["labels_std"] = {}, {} for label in full_labels: - if self.dd.historic_predictions[dk.pair][label].dtype == object: + if pd.api.types.is_string_dtype(self.dd.historic_predictions[dk.pair][label].dtype): continue f = spy.stats.norm.fit(self.dd.historic_predictions[dk.pair][label].tail(num_candles)) dk.data["labels_mean"][label], dk.data["labels_std"][label] = f[0], f[1] @@ -896,7 +896,7 @@ class IFreqaiModel(ABC): ] self.fit_live_predictions(self.dk, self.dk.pair) for label in label_columns: - if dk.full_df[label].dtype == object: + if pd.api.types.is_string_dtype(dk.full_df[label].dtype): continue if "labels_mean" in self.dk.data: dk.full_df.at[index, f"{label}_mean"] = self.dk.data["labels_mean"][ From 51d61bc6a8d2e2cd8013fae306883da1a0ea90ef Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 19:14:33 +0200 Subject: [PATCH 220/315] chore: don't use microsecond precision for --- freqtrade/util/__init__.py | 2 ++ freqtrade/util/datetime_helpers.py | 7 +++++++ tests/strategy/test_interface.py | 9 +++++---- tests/util/test_datetime_helpers.py | 8 ++++++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/freqtrade/util/__init__.py b/freqtrade/util/__init__.py index 06deb3ce0..0e9c1ac12 100644 --- a/freqtrade/util/__init__.py +++ b/freqtrade/util/__init__.py @@ -3,6 +3,7 @@ from freqtrade.util.datetime_helpers import ( dt_from_ts, dt_humanize_delta, dt_now, + dt_now_no_micro, dt_ts, dt_ts_def, dt_ts_none, @@ -39,6 +40,7 @@ __all__ = [ "dt_from_ts", "dt_humanize_delta", "dt_now", + "dt_now_no_micro", "dt_ts", "dt_ts_def", "dt_ts_none", diff --git a/freqtrade/util/datetime_helpers.py b/freqtrade/util/datetime_helpers.py index b6535db5d..55bf29419 100644 --- a/freqtrade/util/datetime_helpers.py +++ b/freqtrade/util/datetime_helpers.py @@ -12,6 +12,13 @@ def dt_now() -> datetime: return datetime.now(UTC) +def dt_now_no_micro() -> datetime: + """Return the current datetime in UTC without microseconds. + Should not be used outside of tests. + """ + return dt_now().replace(microsecond=0) + + def dt_utc( year: int, month: int, diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index f64c2c3cb..bdae9601c 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -22,6 +22,7 @@ from freqtrade.strategy.parameters import ( ) from freqtrade.strategy.strategy_validation import StrategyResultValidator from freqtrade.util import dt_now +from freqtrade.util.datetime_helpers import dt_now_no_micro from tests.conftest import CURRENT_TEST_STRATEGY, TRADE_SIDES, log_has, log_has_re from .strats.strategy_test_v3 import StrategyTestV3 @@ -33,7 +34,7 @@ _STRATEGY.dp = DataProvider({}, None, None) def test_returns_latest_signal(ohlcv_history): - ohlcv_history.loc[1, "date"] = dt_now() + ohlcv_history.loc[1, "date"] = dt_now_no_micro() # Take a copy to correctly modify the call mocked_history = ohlcv_history.copy() mocked_history["enter_long"] = 0 @@ -160,7 +161,7 @@ def test_get_signal_exception_valueerror(mocker, caplog, ohlcv_history): def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history): # default_conf defines a 5m interval. we check interval * 2 + 5m # this is necessary as the last candle is removed (partial candles) by default - ohlcv_history.loc[1, "date"] = dt_now() - timedelta(minutes=16) + ohlcv_history.loc[1, "date"] = dt_now_no_micro() - timedelta(minutes=16) # Take a copy to correctly modify the call mocked_history = ohlcv_history.copy() mocked_history["exit_long"] = 0 @@ -179,7 +180,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history): def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history): # default_conf defines a 5m interval. we check interval * 2 + 5m # this is necessary as the last candle is removed (partial candles) by default - ohlcv_history.loc[1, "date"] = dt_now() + ohlcv_history.loc[1, "date"] = dt_now_no_micro() # Take a copy to correctly modify the call mocked_history = ohlcv_history.copy() # Intentionally don't set sell column @@ -223,7 +224,7 @@ def test_ignore_expired_candle(default_conf): def test_assert_df_raise(mocker, caplog, ohlcv_history): - ohlcv_history.loc[1, "date"] = dt_now() - timedelta(minutes=16) + ohlcv_history.loc[1, "date"] = dt_now_no_micro() - timedelta(minutes=16) # Take a copy to correctly modify the call mocked_history = ohlcv_history.copy() mocked_history["sell"] = 0 diff --git a/tests/util/test_datetime_helpers.py b/tests/util/test_datetime_helpers.py index 9069b60c5..babe5b7b9 100644 --- a/tests/util/test_datetime_helpers.py +++ b/tests/util/test_datetime_helpers.py @@ -6,7 +6,9 @@ import time_machine from freqtrade.util import ( dt_floor_day, dt_from_ts, + dt_humanize_delta, dt_now, + dt_now_no_micro, dt_ts, dt_ts_def, dt_ts_none, @@ -16,15 +18,17 @@ from freqtrade.util import ( format_ms_time_det, shorten_date, ) -from freqtrade.util.datetime_helpers import dt_humanize_delta def test_dt_now(): - with time_machine.travel("2021-09-01 05:01:00 +00:00", tick=False) as t: + with time_machine.travel("2021-09-01 05:01:00.123 +00:00", tick=False) as t: now = datetime.now(UTC) assert dt_now() == now assert dt_ts() == int(now.timestamp() * 1000) assert dt_ts(now) == int(now.timestamp() * 1000) + assert dt_now().microsecond != 0.0 + assert dt_now_no_micro().microsecond == 0.0 + assert dt_now_no_micro() == now.replace(microsecond=0) t.shift(timedelta(hours=5)) assert dt_now() >= now From 12f37b757552e0034f3087f40634681a1e74e9fc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 19:54:44 +0200 Subject: [PATCH 221/315] chore: more generic datetime selection --- freqtrade/rpc/rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 82c66de0c..37c097347 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -1546,7 +1546,7 @@ class RPC: # band-aid until this is fixed: # https://github.com/pandas-dev/pandas/issues/45836 - datetime_types = ["datetime", "datetime64", "datetime64[ns, UTC]"] + datetime_types = ["datetime", "datetime64", "datetimetz"] date_columns = dataframe.select_dtypes(include=datetime_types) for date_column in date_columns: # replace NaT with `None` From c9d4276c669db43413ebdbbbc8322facc2028ffa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 03:03:36 +0000 Subject: [PATCH 222/315] chore(deps-dev): bump ruff from 0.15.8 to 0.15.9 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.8 to 0.15.9. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.8...0.15.9) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.9 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 8018da419..b3d6bdfc8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ -r requirements-freqai-rl.txt -r docs/requirements-docs.txt -ruff==0.15.8 +ruff==0.15.9 mypy==1.20.0 pre-commit==4.5.1 pytest==9.0.2 From df9469d195aae457b077093f89d97db04b520eb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 03:03:59 +0000 Subject: [PATCH 223/315] chore(deps): bump sqlalchemy from 2.0.48 to 2.0.49 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.48 to 2.0.49. - [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-version: 2.0.49 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 f33fd73c2..5017203ef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ technical==1.5.4 ccxt==4.5.47 cryptography==46.0.7 aiohttp==3.13.5 -SQLAlchemy==2.0.48 +SQLAlchemy==2.0.49 python-telegram-bot==22.7 # can't be hard-pinned due to telegram-bot pinning httpx with ~ httpx>=0.24.1 From 94e52f52414bff57071cdf45057e06237c145e0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 03:04:02 +0000 Subject: [PATCH 224/315] chore(deps): bump docker/login-action in the docker group Bumps the docker group with 1 update: [docker/login-action](https://github.com/docker/login-action). Updates `docker/login-action` from 4.0.0 to 4.1.0 - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/b45d80f862d83dbcd57f89517bcf500b2ab88fb2...4907a6ddec9925e35a0a9e82d7399ccc52663121) --- updated-dependencies: - dependency-name: docker/login-action dependency-version: 4.1.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: docker ... Signed-off-by: dependabot[bot] --- .github/workflows/devcontainer-build.yml | 2 +- .github/workflows/docker-build.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/devcontainer-build.yml b/.github/workflows/devcontainer-build.yml index 3cbc8ba6c..d29831375 100644 --- a/.github/workflows/devcontainer-build.yml +++ b/.github/workflows/devcontainer-build.yml @@ -31,7 +31,7 @@ jobs: with: persist-credentials: false - name: Login to GitHub Container Registry - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 7dce79b6a..90cc7c629 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -59,7 +59,7 @@ jobs: uses: ./.github/actions/docker-tags - name: Login to Docker Hub - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -183,13 +183,13 @@ jobs: uses: ./.github/actions/docker-tags - name: Login to Docker Hub - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to github - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io username: ${{ github.actor }} From 2f8d82e0b09842c0d15f022c55bd9e1b93bb0a6a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 03:04:03 +0000 Subject: [PATCH 225/315] chore(deps): bump uvicorn from 0.42.0 to 0.43.0 Bumps [uvicorn](https://github.com/Kludex/uvicorn) from 0.42.0 to 0.43.0. - [Release notes](https://github.com/Kludex/uvicorn/releases) - [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md) - [Commits](https://github.com/Kludex/uvicorn/compare/0.42.0...0.43.0) --- updated-dependencies: - dependency-name: uvicorn dependency-version: 0.43.0 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 f33fd73c2..719156528 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,7 +39,7 @@ sdnotify==0.3.2 # API Server fastapi==0.135.3 pydantic==2.12.5 -uvicorn==0.42.0 +uvicorn==0.43.0 pyjwt==2.12.1 aiofiles==25.1.0 psutil==7.2.2 From 3da3290224138251953ab7f4215663f60b1cb1bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 03:04:11 +0000 Subject: [PATCH 226/315] chore(deps): bump technical from 1.5.4 to 1.6.0 Bumps [technical](https://github.com/freqtrade/technical) from 1.5.4 to 1.6.0. - [Release notes](https://github.com/freqtrade/technical/releases) - [Commits](https://github.com/freqtrade/technical/compare/1.5.4...1.6.0) --- updated-dependencies: - dependency-name: technical dependency-version: 1.6.0 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 f33fd73c2..66ec97438 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ numexpr==2.14.1 # Indicator libraries ft-pandas-ta==0.3.16 ta-lib==0.6.8 -technical==1.5.4 +technical==1.6.0 ccxt==4.5.47 cryptography==46.0.7 From a9b1fd219bea44f618e499cbd5f82715d31bde69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 03:04:23 +0000 Subject: [PATCH 227/315] chore(deps): bump ccxt from 4.5.47 to 4.5.48 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.5.47 to 4.5.48. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Commits](https://github.com/ccxt/ccxt/compare/v4.5.47...v4.5.48) --- updated-dependencies: - dependency-name: ccxt dependency-version: 4.5.48 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 f33fd73c2..d546be536 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ ft-pandas-ta==0.3.16 ta-lib==0.6.8 technical==1.5.4 -ccxt==4.5.47 +ccxt==4.5.48 cryptography==46.0.7 aiohttp==3.13.5 SQLAlchemy==2.0.48 From 895a9f351f6bea282bd61b4c49d0b81fb36b1e77 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 13 Apr 2026 06:32:28 +0200 Subject: [PATCH 228/315] chore: bump sqlalchemy in pre-commit config --- .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 a32221ee3..f3f58d269 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: - types-tabulate==0.10.0.20260308 - types-python-dateutil==2.9.0.20260402 - scipy-stubs==1.17.1.3 - - SQLAlchemy==2.0.48 + - SQLAlchemy==2.0.49 # stages: [push] - repo: https://github.com/charliermarsh/ruff-pre-commit From 187d06b5ba04fcb008f549022bd71ff2690d0139 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 13 Apr 2026 07:09:36 +0200 Subject: [PATCH 229/315] fix: use pd.notna to check for empty strings --- freqtrade/plot/plotting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 085a198ca..ed15c8e79 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -263,7 +263,7 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots: trades["desc"] = trades.apply( lambda row: ( f"{row['profit_ratio']:.2%}, " - + (f"{row['enter_tag']}, " if row["enter_tag"] is not None else "") + + (f"{row['enter_tag']}, " if pd.notna(row["enter_tag"]) else "") + f"{row['exit_reason']}, " + f"{row['trade_duration']} min" ), From 74ba9d76a216673996a560947745fe60ceb79735 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 13 Apr 2026 07:18:35 +0200 Subject: [PATCH 230/315] fix: use as_unit instead of int math --- freqtrade/rpc/rpc.py | 6 ++++-- tests/conftest.py | 2 +- tests/exchange/test_binance_public_data.py | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 37c097347..0b47ecf7c 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, Any import psutil from dateutil.relativedelta import relativedelta from dateutil.tz import tzlocal -from numpy import inf, int64, isnan, mean, nan +from numpy import inf, isnan, mean, nan from pandas import DataFrame, NaT, read_sql from sqlalchemy import func, select @@ -1536,7 +1536,9 @@ class RPC: df_cols = [col for col in dataframe_columns if col in cols_set] dataframe = dataframe.loc[:, df_cols] - dataframe.loc[:, "__date_ts"] = dataframe.loc[:, "date"].dt.as_unit("ms").astype(int64) + dataframe.loc[:, "__date_ts"] = ( + dataframe.loc[:, "date"].dt.as_unit("ms").astype("int64") + ) # Move signal close to separate column when signal for easy plotting for sig_type in signals.keys(): if sig_type in dataframe.columns: diff --git a/tests/conftest.py b/tests/conftest.py index a92144659..46601ddfb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -207,7 +207,7 @@ def generate_test_data( def generate_test_data_raw(timeframe: str, size: int, start: str = "2020-07-05", random_seed=42): """Generates data in the ohlcv format used by ccxt""" df = generate_test_data(timeframe, size, start, random_seed) - df["date"] = df.loc[:, "date"].dt.as_unit("ms").astype(np.int64) + df["date"] = df.loc[:, "date"].dt.as_unit("ms").astype("int64") return list(list(x) for x in zip(*(df[x].values.tolist() for x in df.columns), strict=False)) diff --git a/tests/exchange/test_binance_public_data.py b/tests/exchange/test_binance_public_data.py index ab299321b..98d3864d3 100644 --- a/tests/exchange/test_binance_public_data.py +++ b/tests/exchange/test_binance_public_data.py @@ -69,7 +69,7 @@ def make_response_from_url(start_date, end_date): "taker_buy_quote_volume,ignore" ) df = pd.DataFrame(columns=cols.split(","), dtype=float) - df["open_time"] = date_col.astype("int64") // 10**6 + df["open_time"] = date_col.as_unit("ms").astype("int64") df["open"] = df["high"] = df["low"] = df["close"] = df["volume"] = 1.0 return df From c19982dd36f8597094e2c80f5234b2064c9f5ac8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 13 Apr 2026 07:24:10 +0200 Subject: [PATCH 231/315] chore: use string aliases for astype calls --- freqtrade/data/btanalysis/bt_fileutils.py | 5 ++--- .../data/history/datahandlers/jsondatahandler.py | 3 +-- freqtrade/optimize/analysis/lookahead_helpers.py | 12 ++++++------ tests/exchange_online/test_ccxt_compat.py | 2 +- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/freqtrade/data/btanalysis/bt_fileutils.py b/freqtrade/data/btanalysis/bt_fileutils.py index 2e6b23662..9328ba428 100644 --- a/freqtrade/data/btanalysis/bt_fileutils.py +++ b/freqtrade/data/btanalysis/bt_fileutils.py @@ -10,7 +10,6 @@ from io import BytesIO, StringIO from pathlib import Path from typing import Any, Literal -import numpy as np import pandas as pd from freqtrade.constants import LAST_BT_RESULT_FN @@ -308,7 +307,7 @@ def get_backtest_market_change(filename: Path, include_ts: bool = True) -> pd.Da else: df = pd.read_feather(filename) if include_ts: - df.loc[:, "__date_ts"] = df.loc[:, "date"].dt.as_unit("ms").astype(np.int64) + df.loc[:, "__date_ts"] = df.loc[:, "date"].dt.as_unit("ms").astype("int64") return df @@ -326,7 +325,7 @@ def get_backtest_wallet_change(filename: Path, strategy_name: str) -> pd.DataFra data = load_file_from_zip(filename, f"{filename.stem}_{strategy_name}_wallet.feather") df = pd.read_feather(BytesIO(data)) - df.loc[:, "__date_ts"] = df.loc[:, "date"].dt.as_unit("ms").astype(np.int64) + df.loc[:, "__date_ts"] = df.loc[:, "date"].dt.as_unit("ms").astype("int64") return df except ValueError: pass diff --git a/freqtrade/data/history/datahandlers/jsondatahandler.py b/freqtrade/data/history/datahandlers/jsondatahandler.py index e2ab5c408..332b687b4 100644 --- a/freqtrade/data/history/datahandlers/jsondatahandler.py +++ b/freqtrade/data/history/datahandlers/jsondatahandler.py @@ -1,6 +1,5 @@ import logging -import numpy as np from pandas import DataFrame, read_json, to_datetime from freqtrade import misc @@ -36,7 +35,7 @@ class JsonDataHandler(IDataHandler): self.create_dir_if_needed(filename) _data = data.copy() # Convert date to int (milliseconds) - _data["date"] = _data["date"].dt.as_unit("ms").astype(np.int64) + _data["date"] = _data["date"].dt.as_unit("ms").astype("int64") # Reset index, select only appropriate columns and save as json _data.reset_index(drop=True).loc[:, self._columns].to_json( diff --git a/freqtrade/optimize/analysis/lookahead_helpers.py b/freqtrade/optimize/analysis/lookahead_helpers.py index c9434c3d8..affa0c652 100644 --- a/freqtrade/optimize/analysis/lookahead_helpers.py +++ b/freqtrade/optimize/analysis/lookahead_helpers.py @@ -126,14 +126,14 @@ class LookaheadAnalysisSubFunctions: csv_df = add_or_update_row(csv_df, new_row_data) # Fill NaN values with a default value (e.g., 0) - csv_df["total_signals"] = csv_df["total_signals"].astype(int).fillna(0) - csv_df["biased_entry_signals"] = csv_df["biased_entry_signals"].astype(int).fillna(0) - csv_df["biased_exit_signals"] = csv_df["biased_exit_signals"].astype(int).fillna(0) + csv_df["total_signals"] = csv_df["total_signals"].astype("int64").fillna(0) + csv_df["biased_entry_signals"] = csv_df["biased_entry_signals"].astype("int64").fillna(0) + csv_df["biased_exit_signals"] = csv_df["biased_exit_signals"].astype("int64").fillna(0) # Convert columns to integers - csv_df["total_signals"] = csv_df["total_signals"].astype(int) - csv_df["biased_entry_signals"] = csv_df["biased_entry_signals"].astype(int) - csv_df["biased_exit_signals"] = csv_df["biased_exit_signals"].astype(int) + csv_df["total_signals"] = csv_df["total_signals"].astype("int64") + csv_df["biased_entry_signals"] = csv_df["biased_entry_signals"].astype("int64") + csv_df["biased_exit_signals"] = csv_df["biased_exit_signals"].astype("int64") logger.info(f"saving {config['lookahead_analysis_exportfilename']}") csv_df.to_csv(config["lookahead_analysis_exportfilename"], index=False) diff --git a/tests/exchange_online/test_ccxt_compat.py b/tests/exchange_online/test_ccxt_compat.py index c44bb216a..556d5edf6 100644 --- a/tests/exchange_online/test_ccxt_compat.py +++ b/tests/exchange_online/test_ccxt_compat.py @@ -287,7 +287,7 @@ class TestCCXTExchange: # Check if last-timeframe is within the last 2 intervals now = datetime.now(UTC) - timedelta(minutes=(timeframe_to_minutes(timeframe) * 2)) assert exch.klines(pair_tf).iloc[-1]["date"] >= timeframe_to_prev_date(timeframe, now) - assert exch.klines(pair_tf)["date"].astype(int).iloc[0] // 1e6 == since_ms + assert exch.klines(pair_tf)["date"].dt.as_unit("ms").astype("int64").iloc[0] == since_ms def _ccxt__async_get_candle_history( self, exchange, pair: str, timeframe: str, candle_type: CandleType, factor: float = 0.9 From 755a42699238f05fa8ca857bc3319a74d81e3768 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 12 Apr 2026 08:51:49 +0000 Subject: [PATCH 232/315] chore(deps): bump pandas from 2.3.3 to 3.0.2 Bumps [pandas](https://github.com/pandas-dev/pandas) from 2.3.3 to 3.0.2. - [Release notes](https://github.com/pandas-dev/pandas/releases) - [Commits](https://github.com/pandas-dev/pandas/compare/v2.3.3...v3.0.2) --- updated-dependencies: - dependency-name: pandas dependency-version: 3.0.2 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b62878fad..6836b7b08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ dependencies = [ "urllib3", "jsonschema", "numpy>2.0,<3.0", - "pandas>=2.2.0,<3.0", + "pandas>=2.2.0,<4.0", "TA-Lib<0.7", "ft-pandas-ta", "technical", diff --git a/requirements.txt b/requirements.txt index 2af13fdb7..80c924deb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ numpy==2.4.4 -pandas==2.3.3 +pandas==3.0.2 bottleneck==1.6.0 numexpr==2.14.1 # Indicator libraries From 15bba1b0d0381dac3525c5cb7d0549710b366a70 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 13:24:50 +0200 Subject: [PATCH 233/315] chore: remove some deprecated functions from datakitchen --- freqtrade/freqai/data_kitchen.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py index 9f04e0ca4..16a3bc3c7 100644 --- a/freqtrade/freqai/data_kitchen.py +++ b/freqtrade/freqai/data_kitchen.py @@ -24,8 +24,6 @@ from freqtrade.strategy import merge_informative_pair from freqtrade.strategy.interface import IStrategy -pd.set_option("future.no_silent_downcasting", True) - SECONDS_IN_DAY = 86400 SECONDS_IN_HOUR = 3600 @@ -239,16 +237,14 @@ class FreqaiDataKitchen: filtered_df = filtered_df.replace([np.inf, -np.inf], np.nan) drop_index = pd.isnull(filtered_df).any(axis=1) # get the rows that have NaNs, - drop_index = drop_index.replace(True, 1).replace(False, 0).infer_objects(copy=False) + drop_index = drop_index.replace(True, 1).replace(False, 0).infer_objects() if training_filter: # we don't care about total row number (total no. datapoints) in training, we only care # about removing any row with NaNs # if labels has multiple columns (user wants to train multiple modelEs), we detect here labels = unfiltered_df.filter(label_list or [], axis=1) drop_index_labels = pd.isnull(labels).any(axis=1) - drop_index_labels = ( - drop_index_labels.replace(True, 1).replace(False, 0).infer_objects(copy=False) - ) + drop_index_labels = drop_index_labels.replace(True, 1).replace(False, 0).infer_objects() dates = unfiltered_df["date"] filtered_df = filtered_df[ (drop_index == 0) & (drop_index_labels == 0) From cd5ea969c4cc6a3a2da5a2e803a866b72dcbc1e2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 13:49:17 +0200 Subject: [PATCH 234/315] test: update test pandas frequency usage --- tests/data/test_converter.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/data/test_converter.py b/tests/data/test_converter.py index 835f5a861..4946741aa 100644 --- a/tests/data/test_converter.py +++ b/tests/data/test_converter.py @@ -207,10 +207,13 @@ def test_ohlcv_to_dataframe_multi(timeframe): data1 = data.copy() if timeframe in ("1M", "3M", "1y"): - data1.loc[:, "date"] = data1.loc[:, "date"] + pd.to_timedelta("1w") + data1.loc[:, "date"] = data1.loc[:, "date"] + pd.to_timedelta("1W") else: # Shift by half a timeframe - data1.loc[:, "date"] = data1.loc[:, "date"] + (pd.to_timedelta(timeframe) / 2) + timeframe_f = ( + timeframe.upper() if timeframe.endswith("d") or timeframe.endswith("w") else timeframe + ) + data1.loc[:, "date"] = data1.loc[:, "date"] + (pd.to_timedelta(timeframe_f) / 2) df2 = ohlcv_to_dataframe(data1, timeframe, "UNITTEST/USDT") assert len(df2) == len(data) - 1 From 412e112c0fc5e49a887d0dd3d215479a007f4ab9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 15:59:26 +0200 Subject: [PATCH 235/315] test: fix pandas3 test --- tests/optimize/test_backtesting.py | 3 +++ tests/optimize/test_backtesting_adjust_position.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 70b591d54..95250e7a7 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -860,6 +860,9 @@ def test_backtest_one(default_conf, mocker, testdatadir) -> None: "funding_fees": [0.0, 0.0], } ) + # TODO: pandas3 - create correctly above ?!? + expected["open_date"] = expected["open_date"].astype("datetime64[ms, UTC]") + expected["close_date"] = expected["close_date"].astype("datetime64[ms, UTC]") pd.testing.assert_frame_equal(results, expected) assert "orders" in results.columns data_pair = processed[pair] diff --git a/tests/optimize/test_backtesting_adjust_position.py b/tests/optimize/test_backtesting_adjust_position.py index 33fd87d83..f698173c9 100644 --- a/tests/optimize/test_backtesting_adjust_position.py +++ b/tests/optimize/test_backtesting_adjust_position.py @@ -83,6 +83,9 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) -> "funding_fees": [0.0, 0.0], } ) + # TODO: pandas3 - create correctly above ?!? + expected["open_date"] = expected["open_date"].astype("datetime64[ms, UTC]") + expected["close_date"] = expected["close_date"].astype("datetime64[ms, UTC]") results_no = results.drop(columns=["orders"]) pd.testing.assert_frame_equal(results_no, expected, check_exact=True) From 16547dbdbe20950a59a7c2fd65f56e27ed376cfa Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Apr 2026 19:15:58 +0200 Subject: [PATCH 236/315] fix: improve json trades storing for pandas3 --- freqtrade/data/history/datahandlers/jsondatahandler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/freqtrade/data/history/datahandlers/jsondatahandler.py b/freqtrade/data/history/datahandlers/jsondatahandler.py index 332b687b4..6e63adc87 100644 --- a/freqtrade/data/history/datahandlers/jsondatahandler.py +++ b/freqtrade/data/history/datahandlers/jsondatahandler.py @@ -104,6 +104,9 @@ class JsonDataHandler(IDataHandler): :param trading_mode: Trading mode to use (used to determine the filename) """ filename = self._pair_trades_filename(self._datadir, pair, trading_mode) + # Convert StringDtype columns to object to avoid NaN serialization issues + for col in data.select_dtypes(include="string").columns: + data[col] = data[col].astype(object).where(data[col].notna(), other=None) trades = data.values.tolist() misc.file_dump_json(filename, trades, is_zip=self._use_zip) From 2c5dc729852eb89cb01874d0dd506defabda94d4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 13 Apr 2026 19:54:36 +0200 Subject: [PATCH 237/315] chore: ensure date-column is in ms range --- .../data/history/datahandlers/featherdatahandler.py | 4 ++-- .../data/history/datahandlers/jsondatahandler.py | 2 +- .../data/history/datahandlers/parquetdatahandler.py | 4 ++-- tests/conftest.py | 12 ++++++------ 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/freqtrade/data/history/datahandlers/featherdatahandler.py b/freqtrade/data/history/datahandlers/featherdatahandler.py index ef293d6b2..23d0b2d76 100644 --- a/freqtrade/data/history/datahandlers/featherdatahandler.py +++ b/freqtrade/data/history/datahandlers/featherdatahandler.py @@ -1,6 +1,6 @@ import logging -from pandas import DataFrame, read_feather, to_datetime +from pandas import DataFrame, read_feather from pyarrow import dataset from freqtrade.configuration import TimeRange @@ -71,7 +71,7 @@ class FeatherDataHandler(IDataHandler): "volume": "float", } ) - pairdata["date"] = to_datetime(pairdata["date"], unit="ms", utc=True) + pairdata["date"] = pairdata["date"].dt.as_unit("ms") return pairdata except Exception as e: logger.exception( diff --git a/freqtrade/data/history/datahandlers/jsondatahandler.py b/freqtrade/data/history/datahandlers/jsondatahandler.py index 6e63adc87..a2bd3f6db 100644 --- a/freqtrade/data/history/datahandlers/jsondatahandler.py +++ b/freqtrade/data/history/datahandlers/jsondatahandler.py @@ -80,7 +80,7 @@ class JsonDataHandler(IDataHandler): "volume": "float", } ) - pairdata["date"] = to_datetime(pairdata["date"], unit="ms", utc=True) + pairdata["date"] = to_datetime(pairdata["date"], unit="ms", utc=True).dt.as_unit("ms") return pairdata def ohlcv_append( diff --git a/freqtrade/data/history/datahandlers/parquetdatahandler.py b/freqtrade/data/history/datahandlers/parquetdatahandler.py index 1813f9991..7a5cb39f1 100644 --- a/freqtrade/data/history/datahandlers/parquetdatahandler.py +++ b/freqtrade/data/history/datahandlers/parquetdatahandler.py @@ -1,6 +1,6 @@ import logging -from pandas import DataFrame, read_parquet, to_datetime +from pandas import DataFrame, read_parquet from freqtrade.configuration import TimeRange from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS @@ -68,7 +68,7 @@ class ParquetDataHandler(IDataHandler): "volume": "float", } ) - pairdata["date"] = to_datetime(pairdata["date"], unit="ms", utc=True) + pairdata["date"] = pairdata["date"].dt.as_unit("ms") return pairdata except Exception as e: logger.exception( diff --git a/tests/conftest.py b/tests/conftest.py index 46601ddfb..dc0860466 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -176,20 +176,20 @@ def generate_test_data( base = np.random.normal(base, 2, size=size) if timeframe == "1y": - date = pd.date_range(start, periods=size, freq="1YS", tz="UTC") + date = pd.date_range(start, periods=size, freq="1YS", tz="UTC", unit="ms") elif timeframe == "1M": - date = pd.date_range(start, periods=size, freq="1MS", tz="UTC") + date = pd.date_range(start, periods=size, freq="1MS", tz="UTC", unit="ms") elif timeframe == "3M": - date = pd.date_range(start, periods=size, freq="3MS", tz="UTC") + date = pd.date_range(start, periods=size, freq="3MS", tz="UTC", unit="ms") elif timeframe == "1w" or timeframe == "7d": - date = pd.date_range(start, periods=size, freq="1W-MON", tz="UTC") + date = pd.date_range(start, periods=size, freq="1W-MON", tz="UTC", unit="ms") else: tf_mins = timeframe_to_minutes(timeframe) if tf_mins >= 1: - date = pd.date_range(start, periods=size, freq=f"{tf_mins}min", tz="UTC") + date = pd.date_range(start, periods=size, freq=f"{tf_mins}min", tz="UTC", unit="ms") else: tf_secs = timeframe_to_seconds(timeframe) - date = pd.date_range(start, periods=size, freq=f"{tf_secs}s", tz="UTC") + date = pd.date_range(start, periods=size, freq=f"{tf_secs}s", tz="UTC", unit="ms") df = pd.DataFrame( { "date": date, From 3a31337e43e49fe9df6d0c750c8f6fcb72c8653d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Apr 2026 02:29:25 +0000 Subject: [PATCH 238/315] chore(deps-dev): bump pytest from 9.0.2 to 9.0.3 Bumps [pytest](https://github.com/pytest-dev/pytest) from 9.0.2 to 9.0.3. - [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/9.0.2...9.0.3) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.3 dependency-type: direct:development ... 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 b3d6bdfc8..d8f8c9719 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -9,7 +9,7 @@ ruff==0.15.9 mypy==1.20.0 pre-commit==4.5.1 -pytest==9.0.2 +pytest==9.0.3 pytest-asyncio==1.3.0 pytest-cov==7.1.0 pytest-mock==3.15.1 From 0568c7b945ea5427e3d9e64d639eb3500083b67b Mon Sep 17 00:00:00 2001 From: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com> Date: Tue, 14 Apr 2026 04:01:05 +0000 Subject: [PATCH 239/315] chore: update pre-commit hooks --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f3f58d269..aaf398201 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.20.0" + rev: "v1.20.1" hooks: - id: mypy exclude: build_helpers @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.15.9' + rev: 'v0.15.10' hooks: - id: ruff - id: ruff-format @@ -70,6 +70,6 @@ repos: # Ensure github actions remain safe - repo: https://github.com/woodruffw/zizmor-pre-commit - rev: v1.23.1 + rev: v1.24.1 hooks: - id: zizmor From ff7e6c373720eded7a316076e98c4cc422cfb4b9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Apr 2026 06:34:14 +0200 Subject: [PATCH 240/315] chore: allow pytest to be newer temporarily --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index b62878fad..c94348f4e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -223,6 +223,7 @@ exclude-newer = "1 week" [tool.uv.exclude-newer-package] ccxt = false cryptography = "1 days" +pytest = "5 days" [tool.ruff] line-length = 100 From 812dc64cd75f479b229813eac691bec1dbc1abaf Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Apr 2026 07:18:09 +0200 Subject: [PATCH 241/315] chore: bump cryptography exclusion to 6 days --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c94348f4e..bd0672cd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -222,7 +222,7 @@ exclude-newer = "1 week" [tool.uv.exclude-newer-package] ccxt = false -cryptography = "1 days" +cryptography = "6 days" pytest = "5 days" [tool.ruff] From 7105279654aca2290386f22273f4a15f885bc8fc Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Apr 2026 20:41:18 +0200 Subject: [PATCH 242/315] fix(bitget): handle old and new stoploss order types --- freqtrade/exchange/bitget.py | 54 ++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/freqtrade/exchange/bitget.py b/freqtrade/exchange/bitget.py index 9691f72f8..d3cb1ad83 100644 --- a/freqtrade/exchange/bitget.py +++ b/freqtrade/exchange/bitget.py @@ -101,30 +101,36 @@ class Bitget(Exchange): return order def _fetch_stop_order_fallback(self, order_id: str, pair: str) -> CcxtOrder: - params2 = { - "stop": True, - } - for method in ( - self._api.fetch_open_orders, - self._api.fetch_canceled_and_closed_orders, - ): - try: - orders = method(pair, params=params2) - orders_f = [order for order in orders if order["id"] == order_id] - if orders_f: - order = orders_f[0] - self._log_exchange_response("get_stop_order_fallback", order) - return self._convert_stop_order(pair, order_id, order) - except (ccxt.OrderNotFound, ccxt.InvalidOrder): - pass - except ccxt.DDoSProtection as e: - raise DDosProtection(e) from e - except (ccxt.OperationFailed, ccxt.ExchangeError) as e: - raise TemporaryError( - f"Could not get order due to {e.__class__.__name__}. Message: {e}" - ) from e - except ccxt.BaseError as e: - raise OperationalException(e) from e + # old stoploss orders + paramsold = {"stop": True} + # new stoploss orders with stopLossPrice (used in futures starting 2026.4) + paramsnew = {"planType": "profit_loss"} + params_to_try = ( + (paramsnew, paramsold) if self.trading_mode == TradingMode.FUTURES else (paramsold,) + ) + + for params2 in params_to_try: + for method in ( + self._api.fetch_open_orders, + self._api.fetch_canceled_and_closed_orders, + ): + try: + orders = method(pair, params=params2) + orders_f = [order for order in orders if order["id"] == order_id] + if orders_f: + order = orders_f[0] + self._log_exchange_response("get_stop_order_fallback", order) + return self._convert_stop_order(pair, order_id, order) + except (ccxt.OrderNotFound, ccxt.InvalidOrder): + pass + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.OperationFailed, ccxt.ExchangeError) as e: + raise TemporaryError( + f"Could not get order due to {e.__class__.__name__}. Message: {e}" + ) from e + except ccxt.BaseError as e: + raise OperationalException(e) from e raise RetryableOrderError(f"StoplossOrder not found (pair: {pair} id: {order_id}).") @retrier(retries=API_RETRY_COUNT) From b71f91a15683b8c3d80f40b25666ad5fda507bde Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Apr 2026 21:07:16 +0200 Subject: [PATCH 243/315] test: attempted reduction of test flukes by resetting recwarn --- tests/strategy/test_interface.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index bdae9601c..4ddc35e88 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -1041,6 +1041,7 @@ def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog): ], ) def test_pandas_warning_direct(ohlcv_history, function, raises, recwarn): + recwarn.clear() df = _STRATEGY.populate_indicators(ohlcv_history, {"pair": "ETH/BTC"}) if raises: assert len(recwarn) == 1 @@ -1054,6 +1055,7 @@ def test_pandas_warning_direct(ohlcv_history, function, raises, recwarn): def test_pandas_warning_through_analyze_pair(ohlcv_history, mocker, recwarn): + recwarn.clear() mocker.patch.object(_STRATEGY.dp, "ohlcv", return_value=ohlcv_history) _STRATEGY.analyze_pair("ETH/BTC") assert len(recwarn) == 0, f"warnings: {', '.join(str(w) for w in recwarn.list)}" From b1747fe9eaf730772655665867d1f9ff1b27711c Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 15 Apr 2026 06:47:51 +0200 Subject: [PATCH 244/315] docs: clarify plot_config setup --- docs/plotting.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/plotting.md b/docs/plotting.md index ae480e78f..599a71dbc 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -111,10 +111,10 @@ It also allows multiple subplots to display both MACD and RSI at the same time. Plot type can be configured using `type` key. Possible types are: -* `scatter` corresponding to `plotly.graph_objects.Scatter` class (default). -* `bar` corresponding to `plotly.graph_objects.Bar` class. +* `scatter` corresponding a scatter plot. +* `bar` corresponding to a bar plot. -Extra parameters to `plotly.graph_objects.*` constructor can be specified in `plotly` dict. +Extra parameters to `plotly.graph_objects.*` constructor can be specified in `plotly` dict - these are only supported when using plotly as plotting library and will be ignored when using freq-ui. Sample configuration with inline comments explaining the process: @@ -163,7 +163,7 @@ def plot_config(self): ``` ??? Note "As attribute (former method)" - Assigning plot_config is also possible as Attribute (this used to be the default way). + Assigning `plot_config` is also possible as Attribute (this used to be the default way). This has the disadvantage that strategy parameters are not available, preventing certain configurations from working. ``` python From 63f2a8bb68d8d4348d26d311a5e5ddb423d25c29 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 15 Apr 2026 06:51:32 +0200 Subject: [PATCH 245/315] chore: remove shorter install allows --- pyproject.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bd0672cd2..fd23460bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -222,8 +222,6 @@ exclude-newer = "1 week" [tool.uv.exclude-newer-package] ccxt = false -cryptography = "6 days" -pytest = "5 days" [tool.ruff] line-length = 100 From 0248c209994fa356c10f9daa59e8c1cfb3486feb Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 15 Apr 2026 19:32:33 +0200 Subject: [PATCH 246/315] chore(ci): remove actions/python - uv can do this on it's own --- .github/workflows/binance-lev-tier-update.yml | 4 --- .github/workflows/ci.yml | 28 ++----------------- .github/workflows/deploy-docs.yml | 5 ---- .github/workflows/pre-commit-update.yml | 4 --- 4 files changed, 2 insertions(+), 39 deletions(-) diff --git a/.github/workflows/binance-lev-tier-update.yml b/.github/workflows/binance-lev-tier-update.yml index 01040534e..ed2c21005 100644 --- a/.github/workflows/binance-lev-tier-update.yml +++ b/.github/workflows/binance-lev-tier-update.yml @@ -24,10 +24,6 @@ jobs: with: persist-credentials: false - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: "3.14" - - name: Install uv uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16846d1ac..aa19fd8fa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,11 +32,6 @@ jobs: with: persist-credentials: false - - name: Set up Python 🐍 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: ${{ matrix.python-version }} - - name: Install uv uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: @@ -177,11 +172,6 @@ jobs: with: persist-credentials: false - - name: Set up Python 🐍 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #v6.2.0 - with: - python-version: "3.13" - - name: Install uv uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: @@ -201,7 +191,8 @@ jobs: with: persist-credentials: false - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + - name: Set up Python 🐍 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.13" @@ -219,11 +210,6 @@ jobs: run: | ./tests/test_docs.sh - - name: Set up Python 🐍 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: "3.13" - - name: Install uv uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: @@ -256,11 +242,6 @@ jobs: with: persist-credentials: false - - name: Set up Python 🐍 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: "${{ matrix.python-version }}" - - name: Install uv uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: @@ -328,11 +309,6 @@ jobs: with: persist-credentials: false - - name: Set up Python 🐍 - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: "${{ matrix.python-version }}" - - name: Install uv uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index bd3d0ee9b..fa710a5be 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -26,11 +26,6 @@ jobs: with: persist-credentials: true - - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: '3.13' - - name: Install uv uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml index 3d74af2c1..97cdd6e72 100644 --- a/.github/workflows/pre-commit-update.yml +++ b/.github/workflows/pre-commit-update.yml @@ -25,10 +25,6 @@ jobs: with: persist-credentials: false - - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: "3.13" - - name: Install uv uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: From f1828d72ac1889aa2cd2defda2a6d4ad39aecdcc Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 15 Apr 2026 19:37:02 +0200 Subject: [PATCH 247/315] chore(ci): improved task naming --- .github/workflows/binance-lev-tier-update.yml | 2 +- .github/workflows/ci.yml | 10 +++++----- .github/workflows/deploy-docs.yml | 2 +- .github/workflows/pre-commit-update.yml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/binance-lev-tier-update.yml b/.github/workflows/binance-lev-tier-update.yml index ed2c21005..4354c233b 100644 --- a/.github/workflows/binance-lev-tier-update.yml +++ b/.github/workflows/binance-lev-tier-update.yml @@ -24,7 +24,7 @@ jobs: with: persist-credentials: false - - name: Install uv + - name: Install uv and Python 🐍 uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa19fd8fa..529f3d2cb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: with: persist-credentials: false - - name: Install uv + - name: Install uv and Python 🐍 uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true @@ -172,7 +172,7 @@ jobs: with: persist-credentials: false - - name: Install uv + - name: Install uv and Python 🐍 uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true @@ -210,7 +210,7 @@ jobs: run: | ./tests/test_docs.sh - - name: Install uv + - name: Install uv and Python 🐍 uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true @@ -242,7 +242,7 @@ jobs: with: persist-credentials: false - - name: Install uv + - name: Install uv and Python 🐍 uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true @@ -309,7 +309,7 @@ jobs: with: persist-credentials: false - - name: Install uv + - name: Install uv and Python 🐍 uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index fa710a5be..e30a0edae 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -26,7 +26,7 @@ jobs: with: persist-credentials: true - - name: Install uv + - name: Install uv and Python 🐍 uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml index 97cdd6e72..61da3b15e 100644 --- a/.github/workflows/pre-commit-update.yml +++ b/.github/workflows/pre-commit-update.yml @@ -25,7 +25,7 @@ jobs: with: persist-credentials: false - - name: Install uv + - name: Install uv and Python 🐍 uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 with: activate-environment: true From 12ccd292b113f3f5701d5003e8d97e35db464644 Mon Sep 17 00:00:00 2001 From: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com> Date: Thu, 16 Apr 2026 04:28:50 +0000 Subject: [PATCH 248/315] chore: update binance leverage tiers --- .../exchange/binance_leverage_tiers.json | 3208 ++++++++--------- 1 file changed, 1537 insertions(+), 1671 deletions(-) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 58f8b3286..09b8e5791 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -5,13 +5,13 @@ "symbol": "0G/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 20000.0, + "maxNotional": 5000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 75.0, "info": { "bracket": 1, "initialLeverage": 75, - "notionalCap": 20000, + "notionalCap": 5000, "notionalFloor": 0, "maintMarginRatio": 0.01, "cum": 0.0 @@ -21,136 +21,170 @@ "tier": 2.0, "symbol": "0G/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 200000.0, + "minNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": 2, "initialLeverage": 50, - "notionalCap": 200000, - "notionalFloor": 20000, + "notionalCap": 10000, + "notionalFloor": 5000, "maintMarginRatio": 0.015, - "cum": 100.0 + "cum": 25.0 } }, { "tier": 3.0, "symbol": "0G/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 1000000.0, + "minNotional": 10000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": 3, "initialLeverage": 25, - "notionalCap": 1000000, - "notionalFloor": 200000, + "notionalCap": 50000, + "notionalFloor": 10000, "maintMarginRatio": 0.02, - "cum": 1100.0 + "cum": 75.0 } }, { "tier": 4.0, "symbol": "0G/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": 4, "initialLeverage": 20, - "notionalCap": 2000000, - "notionalFloor": 1000000, + "notionalCap": 100000, + "notionalFloor": 50000, "maintMarginRatio": 0.025, - "cum": 6100.0 + "cum": 325.0 } }, { "tier": 5.0, "symbol": "0G/USDT:USDT", "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 100000.0, + "maxNotional": 175000.0, + "maintenanceMarginRate": 0.0333, + "maxLeverage": 15.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 5000000, - "notionalFloor": 2000000, - "maintMarginRatio": 0.05, - "cum": 56100.0 + "initialLeverage": 15, + "notionalCap": 175000, + "notionalFloor": 100000, + "maintMarginRatio": 0.0333, + "cum": 1155.0 } }, { "tier": 6.0, "symbol": "0G/USDT:USDT", "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 175000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 7500000, - "notionalFloor": 5000000, - "maintMarginRatio": 0.1, - "cum": 306100.0 + "initialLeverage": 10, + "notionalCap": 250000, + "notionalFloor": 175000, + "maintMarginRatio": 0.05, + "cum": 4077.5 } }, { "tier": 7.0, "symbol": "0G/USDT:USDT", "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 250000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 7, - "initialLeverage": 4, - "notionalCap": 10000000, - "notionalFloor": 7500000, - "maintMarginRatio": 0.125, - "cum": 493600.0 + "initialLeverage": 5, + "notionalCap": 750000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1, + "cum": 16577.5 } }, { "tier": 8.0, "symbol": "0G/USDT:USDT", "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 12500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 750000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 8, - "initialLeverage": 2, - "notionalCap": 12500000, - "notionalFloor": 10000000, - "maintMarginRatio": 0.25, - "cum": 1743600.0 + "initialLeverage": 4, + "notionalCap": 1500000, + "notionalFloor": 750000, + "maintMarginRatio": 0.125, + "cum": 35327.5 } }, { "tier": 9.0, "symbol": "0G/USDT:USDT", "currency": "USDT", - "minNotional": 12500000.0, - "maxNotional": 15000000.0, + "minNotional": 1500000.0, + "maxNotional": 4500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 9, + "initialLeverage": 3, + "notionalCap": 4500000, + "notionalFloor": 1500000, + "maintMarginRatio": 0.1667, + "cum": 97877.5 + } + }, + { + "tier": 10.0, + "symbol": "0G/USDT:USDT", + "currency": "USDT", + "minNotional": 4500000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 10, + "initialLeverage": 2, + "notionalCap": 7500000, + "notionalFloor": 4500000, + "maintMarginRatio": 0.25, + "cum": 472727.5 + } + }, + { + "tier": 11.0, + "symbol": "0G/USDT:USDT", + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 9, + "bracket": 11, "initialLeverage": 1, - "notionalCap": 15000000, - "notionalFloor": 12500000, + "notionalCap": 12500000, + "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 4868600.0 + "cum": 2347727.5 } } ], @@ -10126,15 +10160,15 @@ "symbol": "ARIA/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 6000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 1, - "initialLeverage": 50, - "notionalCap": 5000, + "initialLeverage": 10, + "notionalCap": 6000, "notionalFloor": 0, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.05, "cum": 0.0 } }, @@ -10142,119 +10176,85 @@ "tier": 2.0, "symbol": "ARIA/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 6000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 2, - "initialLeverage": 20, - "notionalCap": 10000, - "notionalFloor": 5000, - "maintMarginRatio": 0.025, - "cum": 50.0 + "initialLeverage": 5, + "notionalCap": 50000, + "notionalFloor": 6000, + "maintMarginRatio": 0.1, + "cum": 300.0 } }, { "tier": 3.0, "symbol": "ARIA/USDT:USDT", "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 3, - "initialLeverage": 10, - "notionalCap": 20000, - "notionalFloor": 10000, - "maintMarginRatio": 0.05, - "cum": 300.0 + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 50000, + "maintMarginRatio": 0.125, + "cum": 1550.0 } }, { "tier": 4.0, "symbol": "ARIA/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 100000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 4, - "initialLeverage": 5, - "notionalCap": 50000, - "notionalFloor": 20000, - "maintMarginRatio": 0.1, - "cum": 1300.0 + "initialLeverage": 3, + "notionalCap": 300000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 5720.0 } }, { "tier": 5.0, "symbol": "ARIA/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 300000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 5, - "initialLeverage": 4, - "notionalCap": 250000, - "notionalFloor": 50000, - "maintMarginRatio": 0.125, - "cum": 2550.0 + "initialLeverage": 2, + "notionalCap": 1000000, + "notionalFloor": 300000, + "maintMarginRatio": 0.25, + "cum": 30710.0 } }, { "tier": 6.0, "symbol": "ARIA/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 6, - "initialLeverage": 3, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.1667, - "cum": 12975.0 - } - }, - { - "tier": 7.0, - "symbol": "ARIA/USDT:USDT", - "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 7500000, - "notionalFloor": 500000, - "maintMarginRatio": 0.25, - "cum": 54625.0 - } - }, - { - "tier": 8.0, - "symbol": "ARIA/USDT:USDT", - "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 12500000.0, + "minNotional": 1000000.0, + "maxNotional": 2000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 6, "initialLeverage": 1, - "notionalCap": 12500000, - "notionalFloor": 7500000, + "notionalCap": 2000000, + "notionalFloor": 1000000, "maintMarginRatio": 0.5, - "cum": 1929625.0 + "cum": 280710.0 } } ], @@ -11402,15 +11402,15 @@ "symbol": "ATH/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, - "notionalCap": 5000, + "initialLeverage": 50, + "notionalCap": 10000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -11418,38 +11418,21 @@ "tier": 2.0, "symbol": "ATH/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, - "info": { - "bracket": 2, - "initialLeverage": 50, - "notionalCap": 10000, - "notionalFloor": 5000, - "maintMarginRatio": 0.015, - "cum": 25.0 - } - }, - { - "tier": 3.0, - "symbol": "ATH/USDT:USDT", - "currency": "USDT", "minNotional": 10000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { - "bracket": 3, + "bracket": 2, "initialLeverage": 25, "notionalCap": 50000, "notionalFloor": 10000, "maintMarginRatio": 0.02, - "cum": 75.0 + "cum": 50.0 } }, { - "tier": 4.0, + "tier": 3.0, "symbol": "ATH/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, @@ -11457,50 +11440,50 @@ "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { - "bracket": 4, + "bracket": 3, "initialLeverage": 20, "notionalCap": 100000, "notionalFloor": 50000, "maintMarginRatio": 0.025, - "cum": 325.0 + "cum": 300.0 + } + }, + { + "tier": 4.0, + "symbol": "ATH/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.0333, + "maxLeverage": 15.0, + "info": { + "bracket": 4, + "initialLeverage": 15, + "notionalCap": 125000, + "notionalFloor": 100000, + "maintMarginRatio": 0.0333, + "cum": 1130.0 } }, { "tier": 5.0, "symbol": "ATH/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 175000.0, - "maintenanceMarginRate": 0.0333, - "maxLeverage": 15.0, - "info": { - "bracket": 5, - "initialLeverage": 15, - "notionalCap": 175000, - "notionalFloor": 100000, - "maintMarginRatio": 0.0333, - "cum": 1155.0 - } - }, - { - "tier": 6.0, - "symbol": "ATH/USDT:USDT", - "currency": "USDT", - "minNotional": 175000.0, + "minNotional": 125000.0, "maxNotional": 250000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { - "bracket": 6, + "bracket": 5, "initialLeverage": 10, "notionalCap": 250000, - "notionalFloor": 175000, + "notionalFloor": 125000, "maintMarginRatio": 0.05, - "cum": 4077.5 + "cum": 3217.5 } }, { - "tier": 7.0, + "tier": 6.0, "symbol": "ATH/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, @@ -11508,16 +11491,16 @@ "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": 7, + "bracket": 6, "initialLeverage": 5, "notionalCap": 750000, "notionalFloor": 250000, "maintMarginRatio": 0.1, - "cum": 16577.5 + "cum": 15717.5 } }, { - "tier": 8.0, + "tier": 7.0, "symbol": "ATH/USDT:USDT", "currency": "USDT", "minNotional": 750000.0, @@ -11525,63 +11508,63 @@ "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 8, + "bracket": 7, "initialLeverage": 4, "notionalCap": 1500000, "notionalFloor": 750000, "maintMarginRatio": 0.125, - "cum": 35327.5 + "cum": 34467.5 + } + }, + { + "tier": 8.0, + "symbol": "ATH/USDT:USDT", + "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 2500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 8, + "initialLeverage": 3, + "notionalCap": 2500000, + "notionalFloor": 1500000, + "maintMarginRatio": 0.1667, + "cum": 97017.5 } }, { "tier": 9.0, "symbol": "ATH/USDT:USDT", "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 4500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, + "minNotional": 2500000.0, + "maxNotional": 5000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 9, - "initialLeverage": 3, - "notionalCap": 4500000, - "notionalFloor": 1500000, - "maintMarginRatio": 0.1667, - "cum": 97877.5 + "initialLeverage": 2, + "notionalCap": 5000000, + "notionalFloor": 2500000, + "maintMarginRatio": 0.25, + "cum": 305267.5 } }, { "tier": 10.0, "symbol": "ATH/USDT:USDT", "currency": "USDT", - "minNotional": 4500000.0, + "minNotional": 5000000.0, "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 10, - "initialLeverage": 2, - "notionalCap": 7500000, - "notionalFloor": 4500000, - "maintMarginRatio": 0.25, - "cum": 472727.5 - } - }, - { - "tier": 11.0, - "symbol": "ATH/USDT:USDT", - "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 12500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 11, + "bracket": 10, "initialLeverage": 1, - "notionalCap": 12500000, - "notionalFloor": 7500000, + "notionalCap": 7500000, + "notionalFloor": 5000000, "maintMarginRatio": 0.5, - "cum": 2347727.5 + "cum": 1555267.5 } } ], @@ -16589,13 +16572,13 @@ "symbol": "BERA/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 50000.0, + "maxNotional": 25000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": 3, "initialLeverage": 25, - "notionalCap": 50000, + "notionalCap": 25000, "notionalFloor": 10000, "maintMarginRatio": 0.02, "cum": 75.0 @@ -16605,51 +16588,51 @@ "tier": 4.0, "symbol": "BERA/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, + "minNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": 4, "initialLeverage": 20, - "notionalCap": 100000, - "notionalFloor": 50000, + "notionalCap": 50000, + "notionalFloor": 25000, "maintMarginRatio": 0.025, - "cum": 325.0 + "cum": 200.0 } }, { "tier": 5.0, "symbol": "BERA/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 175000.0, - "maintenanceMarginRate": 0.0333, - "maxLeverage": 15.0, + "minNotional": 50000.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 5, - "initialLeverage": 15, - "notionalCap": 175000, - "notionalFloor": 100000, - "maintMarginRatio": 0.0333, - "cum": 1155.0 + "initialLeverage": 10, + "notionalCap": 125000, + "notionalFloor": 50000, + "maintMarginRatio": 0.05, + "cum": 1450.0 } }, { "tier": 6.0, "symbol": "BERA/USDT:USDT", "currency": "USDT", - "minNotional": 175000.0, + "minNotional": 125000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 6, - "initialLeverage": 10, + "initialLeverage": 5, "notionalCap": 250000, - "notionalFloor": 175000, - "maintMarginRatio": 0.05, - "cum": 4077.5 + "notionalFloor": 125000, + "maintMarginRatio": 0.1, + "cum": 7700.0 } }, { @@ -16657,71 +16640,54 @@ "symbol": "BERA/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 7, - "initialLeverage": 5, - "notionalCap": 750000, + "initialLeverage": 4, + "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1, - "cum": 16577.5 + "maintMarginRatio": 0.125, + "cum": 13950.0 } }, { "tier": 8.0, "symbol": "BERA/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 8, - "initialLeverage": 4, - "notionalCap": 1500000, - "notionalFloor": 750000, - "maintMarginRatio": 0.125, - "cum": 35327.5 + "initialLeverage": 3, + "notionalCap": 1000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.1667, + "cum": 34800.0 } }, { "tier": 9.0, "symbol": "BERA/USDT:USDT", "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 4500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 9, - "initialLeverage": 3, - "notionalCap": 4500000, - "notionalFloor": 1500000, - "maintMarginRatio": 0.1667, - "cum": 97877.5 - } - }, - { - "tier": 10.0, - "symbol": "BERA/USDT:USDT", - "currency": "USDT", - "minNotional": 4500000.0, + "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 10, + "bracket": 9, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 4500000, + "notionalFloor": 1000000, "maintMarginRatio": 0.25, - "cum": 472727.5 + "cum": 118100.0 } }, { - "tier": 11.0, + "tier": 10.0, "symbol": "BERA/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -16729,12 +16695,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 11, + "bracket": 10, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 2347727.5 + "cum": 1993100.0 } } ], @@ -17348,14 +17314,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -17365,14 +17331,14 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 25, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.02, "cum": 25.0 } }, @@ -17381,15 +17347,15 @@ "symbol": "BIO/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 50000, + "initialLeverage": 20, + "notionalCap": 25000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, + "maintMarginRatio": 0.025, "cum": 75.0 } }, @@ -17397,51 +17363,51 @@ "tier": 4.0, "symbol": "BIO/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 25000.0, + "maxNotional": 62500.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 4, - "initialLeverage": 20, - "notionalCap": 100000, - "notionalFloor": 50000, - "maintMarginRatio": 0.025, - "cum": 325.0 + "initialLeverage": 10, + "notionalCap": 62500, + "notionalFloor": 25000, + "maintMarginRatio": 0.05, + "cum": 700.0 } }, { "tier": 5.0, "symbol": "BIO/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 175000.0, - "maintenanceMarginRate": 0.0333, - "maxLeverage": 15.0, + "minNotional": 62500.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 5, - "initialLeverage": 15, - "notionalCap": 175000, - "notionalFloor": 100000, - "maintMarginRatio": 0.0333, - "cum": 1155.0 + "initialLeverage": 5, + "notionalCap": 125000, + "notionalFloor": 62500, + "maintMarginRatio": 0.1, + "cum": 3825.0 } }, { "tier": 6.0, "symbol": "BIO/USDT:USDT", "currency": "USDT", - "minNotional": 175000.0, + "minNotional": 125000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 6, - "initialLeverage": 10, + "initialLeverage": 4, "notionalCap": 250000, - "notionalFloor": 175000, - "maintMarginRatio": 0.05, - "cum": 4077.5 + "notionalFloor": 125000, + "maintMarginRatio": 0.125, + "cum": 6950.0 } }, { @@ -17449,71 +17415,37 @@ "symbol": "BIO/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 7, - "initialLeverage": 5, - "notionalCap": 750000, + "initialLeverage": 3, + "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1, - "cum": 16577.5 + "maintMarginRatio": 0.1667, + "cum": 17375.0 } }, { "tier": 8.0, "symbol": "BIO/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 8, - "initialLeverage": 4, - "notionalCap": 1500000, - "notionalFloor": 750000, - "maintMarginRatio": 0.125, - "cum": 35327.5 - } - }, - { - "tier": 9.0, - "symbol": "BIO/USDT:USDT", - "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 4500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 9, - "initialLeverage": 3, - "notionalCap": 4500000, - "notionalFloor": 1500000, - "maintMarginRatio": 0.1667, - "cum": 97877.5 - } - }, - { - "tier": 10.0, - "symbol": "BIO/USDT:USDT", - "currency": "USDT", - "minNotional": 4500000.0, + "minNotional": 500000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 4500000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 472727.5 + "cum": 59025.0 } }, { - "tier": 11.0, + "tier": 9.0, "symbol": "BIO/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -17521,12 +17453,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 11, + "bracket": 9, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 2347727.5 + "cum": 1934025.0 } } ], @@ -33401,14 +33333,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -33418,14 +33350,14 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 25, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.02, "cum": 25.0 } }, @@ -33434,15 +33366,15 @@ "symbol": "ERA/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 50000, + "initialLeverage": 20, + "notionalCap": 25000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, + "maintMarginRatio": 0.025, "cum": 75.0 } }, @@ -33450,51 +33382,51 @@ "tier": 4.0, "symbol": "ERA/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 25000.0, + "maxNotional": 62500.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 4, - "initialLeverage": 20, - "notionalCap": 100000, - "notionalFloor": 50000, - "maintMarginRatio": 0.025, - "cum": 325.0 + "initialLeverage": 10, + "notionalCap": 62500, + "notionalFloor": 25000, + "maintMarginRatio": 0.05, + "cum": 700.0 } }, { "tier": 5.0, "symbol": "ERA/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 175000.0, - "maintenanceMarginRate": 0.0333, - "maxLeverage": 15.0, + "minNotional": 62500.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 5, - "initialLeverage": 15, - "notionalCap": 175000, - "notionalFloor": 100000, - "maintMarginRatio": 0.0333, - "cum": 1155.0 + "initialLeverage": 5, + "notionalCap": 125000, + "notionalFloor": 62500, + "maintMarginRatio": 0.1, + "cum": 3825.0 } }, { "tier": 6.0, "symbol": "ERA/USDT:USDT", "currency": "USDT", - "minNotional": 175000.0, + "minNotional": 125000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 6, - "initialLeverage": 10, + "initialLeverage": 4, "notionalCap": 250000, - "notionalFloor": 175000, - "maintMarginRatio": 0.05, - "cum": 4077.5 + "notionalFloor": 125000, + "maintMarginRatio": 0.125, + "cum": 6950.0 } }, { @@ -33502,71 +33434,37 @@ "symbol": "ERA/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 7, - "initialLeverage": 5, - "notionalCap": 750000, + "initialLeverage": 3, + "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1, - "cum": 16577.5 + "maintMarginRatio": 0.1667, + "cum": 17375.0 } }, { "tier": 8.0, "symbol": "ERA/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 8, - "initialLeverage": 4, - "notionalCap": 1500000, - "notionalFloor": 750000, - "maintMarginRatio": 0.125, - "cum": 35327.5 - } - }, - { - "tier": 9.0, - "symbol": "ERA/USDT:USDT", - "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 4500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 9, - "initialLeverage": 3, - "notionalCap": 4500000, - "notionalFloor": 1500000, - "maintMarginRatio": 0.1667, - "cum": 97877.5 - } - }, - { - "tier": 10.0, - "symbol": "ERA/USDT:USDT", - "currency": "USDT", - "minNotional": 4500000.0, + "minNotional": 500000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 4500000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 472727.5 + "cum": 59025.0 } }, { - "tier": 11.0, + "tier": 9.0, "symbol": "ERA/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -33574,12 +33472,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 11, + "bracket": 9, "initialLeverage": 1, "notionalCap": 12000000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 2347727.5 + "cum": 1934025.0 } } ], @@ -36500,13 +36398,13 @@ "symbol": "FF/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 20000.0, + "maxNotional": 5000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 75.0, "info": { "bracket": 1, "initialLeverage": 75, - "notionalCap": 20000, + "notionalCap": 5000, "notionalFloor": 0, "maintMarginRatio": 0.01, "cum": 0.0 @@ -36516,136 +36414,170 @@ "tier": 2.0, "symbol": "FF/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 200000.0, + "minNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": 2, "initialLeverage": 50, - "notionalCap": 200000, - "notionalFloor": 20000, + "notionalCap": 10000, + "notionalFloor": 5000, "maintMarginRatio": 0.015, - "cum": 100.0 + "cum": 25.0 } }, { "tier": 3.0, "symbol": "FF/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 1000000.0, + "minNotional": 10000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": 3, "initialLeverage": 25, - "notionalCap": 1000000, - "notionalFloor": 200000, + "notionalCap": 50000, + "notionalFloor": 10000, "maintMarginRatio": 0.02, - "cum": 1100.0 + "cum": 75.0 } }, { "tier": 4.0, "symbol": "FF/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": 4, "initialLeverage": 20, - "notionalCap": 2000000, - "notionalFloor": 1000000, + "notionalCap": 100000, + "notionalFloor": 50000, "maintMarginRatio": 0.025, - "cum": 6100.0 + "cum": 325.0 } }, { "tier": 5.0, "symbol": "FF/USDT:USDT", "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 100000.0, + "maxNotional": 175000.0, + "maintenanceMarginRate": 0.0333, + "maxLeverage": 15.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 5000000, - "notionalFloor": 2000000, - "maintMarginRatio": 0.05, - "cum": 56100.0 + "initialLeverage": 15, + "notionalCap": 175000, + "notionalFloor": 100000, + "maintMarginRatio": 0.0333, + "cum": 1155.0 } }, { "tier": 6.0, "symbol": "FF/USDT:USDT", "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 175000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 7500000, - "notionalFloor": 5000000, - "maintMarginRatio": 0.1, - "cum": 306100.0 + "initialLeverage": 10, + "notionalCap": 250000, + "notionalFloor": 175000, + "maintMarginRatio": 0.05, + "cum": 4077.5 } }, { "tier": 7.0, "symbol": "FF/USDT:USDT", "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 250000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 7, - "initialLeverage": 4, - "notionalCap": 10000000, - "notionalFloor": 7500000, - "maintMarginRatio": 0.125, - "cum": 493600.0 + "initialLeverage": 5, + "notionalCap": 750000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1, + "cum": 16577.5 } }, { "tier": 8.0, "symbol": "FF/USDT:USDT", "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 12500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 750000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 8, - "initialLeverage": 2, - "notionalCap": 12500000, - "notionalFloor": 10000000, - "maintMarginRatio": 0.25, - "cum": 1743600.0 + "initialLeverage": 4, + "notionalCap": 1500000, + "notionalFloor": 750000, + "maintMarginRatio": 0.125, + "cum": 35327.5 } }, { "tier": 9.0, "symbol": "FF/USDT:USDT", "currency": "USDT", - "minNotional": 12500000.0, - "maxNotional": 15000000.0, + "minNotional": 1500000.0, + "maxNotional": 4500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 9, + "initialLeverage": 3, + "notionalCap": 4500000, + "notionalFloor": 1500000, + "maintMarginRatio": 0.1667, + "cum": 97877.5 + } + }, + { + "tier": 10.0, + "symbol": "FF/USDT:USDT", + "currency": "USDT", + "minNotional": 4500000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 10, + "initialLeverage": 2, + "notionalCap": 7500000, + "notionalFloor": 4500000, + "maintMarginRatio": 0.25, + "cum": 472727.5 + } + }, + { + "tier": 11.0, + "symbol": "FF/USDT:USDT", + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 9, + "bracket": 11, "initialLeverage": 1, - "notionalCap": 15000000, - "notionalFloor": 12500000, + "notionalCap": 12500000, + "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 4868600.0 + "cum": 2347727.5 } } ], @@ -40065,6 +39997,127 @@ } } ], + "GENIUS/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "GENIUS/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": 1, + "initialLeverage": 20, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.025, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "GENIUS/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 2, + "initialLeverage": 10, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.05, + "cum": 125.0 + } + }, + { + "tier": 3.0, + "symbol": "GENIUS/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 3, + "initialLeverage": 5, + "notionalCap": 50000, + "notionalFloor": 10000, + "maintMarginRatio": 0.1, + "cum": 625.0 + } + }, + { + "tier": 4.0, + "symbol": "GENIUS/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 4, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 50000, + "maintMarginRatio": 0.125, + "cum": 1875.0 + } + }, + { + "tier": 5.0, + "symbol": "GENIUS/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 5, + "initialLeverage": 3, + "notionalCap": 250000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 6045.0 + } + }, + { + "tier": 6.0, + "symbol": "GENIUS/USDT:USDT", + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 6, + "initialLeverage": 2, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.25, + "cum": 26870.0 + } + }, + { + "tier": 7.0, + "symbol": "GENIUS/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 7, + "initialLeverage": 1, + "notionalCap": 800000, + "notionalFloor": 500000, + "maintMarginRatio": 0.5, + "cum": 151870.0 + } + } + ], "GHST/USDT:USDT": [ { "tier": 1.0, @@ -49810,13 +49863,13 @@ "symbol": "KAITO/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 50000.0, + "maxNotional": 25000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": 3, "initialLeverage": 25, - "notionalCap": 50000, + "notionalCap": 25000, "notionalFloor": 10000, "maintMarginRatio": 0.02, "cum": 75.0 @@ -49826,51 +49879,51 @@ "tier": 4.0, "symbol": "KAITO/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, + "minNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": 4, "initialLeverage": 20, - "notionalCap": 100000, - "notionalFloor": 50000, + "notionalCap": 50000, + "notionalFloor": 25000, "maintMarginRatio": 0.025, - "cum": 325.0 + "cum": 200.0 } }, { "tier": 5.0, "symbol": "KAITO/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 175000.0, - "maintenanceMarginRate": 0.0333, - "maxLeverage": 15.0, + "minNotional": 50000.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 5, - "initialLeverage": 15, - "notionalCap": 175000, - "notionalFloor": 100000, - "maintMarginRatio": 0.0333, - "cum": 1155.0 + "initialLeverage": 10, + "notionalCap": 125000, + "notionalFloor": 50000, + "maintMarginRatio": 0.05, + "cum": 1450.0 } }, { "tier": 6.0, "symbol": "KAITO/USDT:USDT", "currency": "USDT", - "minNotional": 175000.0, + "minNotional": 125000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 6, - "initialLeverage": 10, + "initialLeverage": 5, "notionalCap": 250000, - "notionalFloor": 175000, - "maintMarginRatio": 0.05, - "cum": 4077.5 + "notionalFloor": 125000, + "maintMarginRatio": 0.1, + "cum": 7700.0 } }, { @@ -49878,71 +49931,54 @@ "symbol": "KAITO/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 7, - "initialLeverage": 5, - "notionalCap": 750000, + "initialLeverage": 4, + "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1, - "cum": 16577.5 + "maintMarginRatio": 0.125, + "cum": 13950.0 } }, { "tier": 8.0, "symbol": "KAITO/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 8, - "initialLeverage": 4, - "notionalCap": 1500000, - "notionalFloor": 750000, - "maintMarginRatio": 0.125, - "cum": 35327.5 + "initialLeverage": 3, + "notionalCap": 1000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.1667, + "cum": 34800.0 } }, { "tier": 9.0, "symbol": "KAITO/USDT:USDT", "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 4500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 9, - "initialLeverage": 3, - "notionalCap": 4500000, - "notionalFloor": 1500000, - "maintMarginRatio": 0.1667, - "cum": 97877.5 - } - }, - { - "tier": 10.0, - "symbol": "KAITO/USDT:USDT", - "currency": "USDT", - "minNotional": 4500000.0, + "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 10, + "bracket": 9, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 4500000, + "notionalFloor": 1000000, "maintMarginRatio": 0.25, - "cum": 472727.5 + "cum": 118100.0 } }, { - "tier": 11.0, + "tier": 10.0, "symbol": "KAITO/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -49950,12 +49986,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 11, + "bracket": 10, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 2347727.5 + "cum": 1993100.0 } } ], @@ -52742,15 +52778,15 @@ "symbol": "LINEA/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 7500.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, - "notionalCap": 7500, + "initialLeverage": 50, + "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -52758,170 +52794,136 @@ "tier": 2.0, "symbol": "LINEA/USDT:USDT", "currency": "USDT", - "minNotional": 7500.0, - "maxNotional": 15000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": 2, - "initialLeverage": 50, - "notionalCap": 15000, - "notionalFloor": 7500, - "maintMarginRatio": 0.015, - "cum": 37.5 + "initialLeverage": 25, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.02, + "cum": 25.0 } }, { "tier": 3.0, "symbol": "LINEA/USDT:USDT", "currency": "USDT", - "minNotional": 15000.0, - "maxNotional": 75000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 10000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 75000, - "notionalFloor": 15000, - "maintMarginRatio": 0.02, - "cum": 112.5 + "initialLeverage": 20, + "notionalCap": 25000, + "notionalFloor": 10000, + "maintMarginRatio": 0.025, + "cum": 75.0 } }, { "tier": 4.0, "symbol": "LINEA/USDT:USDT", "currency": "USDT", - "minNotional": 75000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 25000.0, + "maxNotional": 62500.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 4, - "initialLeverage": 20, - "notionalCap": 200000, - "notionalFloor": 75000, - "maintMarginRatio": 0.025, - "cum": 487.5 + "initialLeverage": 10, + "notionalCap": 62500, + "notionalFloor": 25000, + "maintMarginRatio": 0.05, + "cum": 700.0 } }, { "tier": 5.0, "symbol": "LINEA/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 350000.0, - "maintenanceMarginRate": 0.0333, - "maxLeverage": 15.0, + "minNotional": 62500.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 5, - "initialLeverage": 15, - "notionalCap": 350000, - "notionalFloor": 200000, - "maintMarginRatio": 0.0333, - "cum": 2147.5 + "initialLeverage": 5, + "notionalCap": 125000, + "notionalFloor": 62500, + "maintMarginRatio": 0.1, + "cum": 3825.0 } }, { "tier": 6.0, "symbol": "LINEA/USDT:USDT", "currency": "USDT", - "minNotional": 350000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 125000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 6, - "initialLeverage": 10, - "notionalCap": 750000, - "notionalFloor": 350000, - "maintMarginRatio": 0.05, - "cum": 7992.5 + "initialLeverage": 4, + "notionalCap": 250000, + "notionalFloor": 125000, + "maintMarginRatio": 0.125, + "cum": 6950.0 } }, { "tier": 7.0, "symbol": "LINEA/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 7, - "initialLeverage": 5, - "notionalCap": 3000000, - "notionalFloor": 750000, - "maintMarginRatio": 0.1, - "cum": 45492.5 + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 17375.0 } }, { "tier": 8.0, "symbol": "LINEA/USDT:USDT", "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 4500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 500000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 8, - "initialLeverage": 4, - "notionalCap": 4500000, - "notionalFloor": 3000000, - "maintMarginRatio": 0.125, - "cum": 120492.5 + "initialLeverage": 2, + "notionalCap": 7500000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 59025.0 } }, { "tier": 9.0, "symbol": "LINEA/USDT:USDT", "currency": "USDT", - "minNotional": 4500000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 9, - "initialLeverage": 3, - "notionalCap": 7500000, - "notionalFloor": 4500000, - "maintMarginRatio": 0.1667, - "cum": 308142.5 - } - }, - { - "tier": 10.0, - "symbol": "LINEA/USDT:USDT", - "currency": "USDT", "minNotional": 7500000.0, - "maxNotional": 12000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 10, - "initialLeverage": 2, - "notionalCap": 12000000, - "notionalFloor": 7500000, - "maintMarginRatio": 0.25, - "cum": 932892.5 - } - }, - { - "tier": 11.0, - "symbol": "LINEA/USDT:USDT", - "currency": "USDT", - "minNotional": 12000000.0, - "maxNotional": 18000000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 11, + "bracket": 9, "initialLeverage": 1, - "notionalCap": 18000000, - "notionalFloor": 12000000, + "notionalCap": 12500000, + "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 3932892.5 + "cum": 1934025.0 } } ], @@ -59465,14 +59467,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -59482,14 +59484,14 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 25, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.02, "cum": 25.0 } }, @@ -59498,15 +59500,15 @@ "symbol": "MOODENG/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 50000, + "initialLeverage": 20, + "notionalCap": 25000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, + "maintMarginRatio": 0.025, "cum": 75.0 } }, @@ -59514,51 +59516,51 @@ "tier": 4.0, "symbol": "MOODENG/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 25000.0, + "maxNotional": 62500.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 4, - "initialLeverage": 20, - "notionalCap": 100000, - "notionalFloor": 50000, - "maintMarginRatio": 0.025, - "cum": 325.0 + "initialLeverage": 10, + "notionalCap": 62500, + "notionalFloor": 25000, + "maintMarginRatio": 0.05, + "cum": 700.0 } }, { "tier": 5.0, "symbol": "MOODENG/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 175000.0, - "maintenanceMarginRate": 0.0333, - "maxLeverage": 15.0, + "minNotional": 62500.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 5, - "initialLeverage": 15, - "notionalCap": 175000, - "notionalFloor": 100000, - "maintMarginRatio": 0.0333, - "cum": 1155.0 + "initialLeverage": 5, + "notionalCap": 125000, + "notionalFloor": 62500, + "maintMarginRatio": 0.1, + "cum": 3825.0 } }, { "tier": 6.0, "symbol": "MOODENG/USDT:USDT", "currency": "USDT", - "minNotional": 175000.0, + "minNotional": 125000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 6, - "initialLeverage": 10, + "initialLeverage": 4, "notionalCap": 250000, - "notionalFloor": 175000, - "maintMarginRatio": 0.05, - "cum": 4077.5 + "notionalFloor": 125000, + "maintMarginRatio": 0.125, + "cum": 6950.0 } }, { @@ -59566,71 +59568,37 @@ "symbol": "MOODENG/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 7, - "initialLeverage": 5, - "notionalCap": 750000, + "initialLeverage": 3, + "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1, - "cum": 16577.5 + "maintMarginRatio": 0.1667, + "cum": 17375.0 } }, { "tier": 8.0, "symbol": "MOODENG/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 8, - "initialLeverage": 4, - "notionalCap": 1500000, - "notionalFloor": 750000, - "maintMarginRatio": 0.125, - "cum": 35327.5 - } - }, - { - "tier": 9.0, - "symbol": "MOODENG/USDT:USDT", - "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 4500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 9, - "initialLeverage": 3, - "notionalCap": 4500000, - "notionalFloor": 1500000, - "maintMarginRatio": 0.1667, - "cum": 97877.5 - } - }, - { - "tier": 10.0, - "symbol": "MOODENG/USDT:USDT", - "currency": "USDT", - "minNotional": 4500000.0, + "minNotional": 500000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 4500000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 472727.5 + "cum": 59025.0 } }, { - "tier": 11.0, + "tier": 9.0, "symbol": "MOODENG/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -59638,12 +59606,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 11, + "bracket": 9, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 2347727.5 + "cum": 1934025.0 } } ], @@ -62740,13 +62708,13 @@ "symbol": "NMR/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 50000.0, + "maxNotional": 25000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": 3, "initialLeverage": 25, - "notionalCap": 50000, + "notionalCap": 25000, "notionalFloor": 10000, "maintMarginRatio": 0.02, "cum": 75.0 @@ -62756,51 +62724,51 @@ "tier": 4.0, "symbol": "NMR/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, + "minNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": 4, "initialLeverage": 20, - "notionalCap": 100000, - "notionalFloor": 50000, + "notionalCap": 50000, + "notionalFloor": 25000, "maintMarginRatio": 0.025, - "cum": 325.0 + "cum": 200.0 } }, { "tier": 5.0, "symbol": "NMR/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 175000.0, - "maintenanceMarginRate": 0.0333, - "maxLeverage": 15.0, + "minNotional": 50000.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 5, - "initialLeverage": 15, - "notionalCap": 175000, - "notionalFloor": 100000, - "maintMarginRatio": 0.0333, - "cum": 1155.0 + "initialLeverage": 10, + "notionalCap": 125000, + "notionalFloor": 50000, + "maintMarginRatio": 0.05, + "cum": 1450.0 } }, { "tier": 6.0, "symbol": "NMR/USDT:USDT", "currency": "USDT", - "minNotional": 175000.0, + "minNotional": 125000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 6, - "initialLeverage": 10, + "initialLeverage": 5, "notionalCap": 250000, - "notionalFloor": 175000, - "maintMarginRatio": 0.05, - "cum": 4077.5 + "notionalFloor": 125000, + "maintMarginRatio": 0.1, + "cum": 7700.0 } }, { @@ -62808,71 +62776,54 @@ "symbol": "NMR/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 7, - "initialLeverage": 5, - "notionalCap": 750000, + "initialLeverage": 4, + "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1, - "cum": 16577.5 + "maintMarginRatio": 0.125, + "cum": 13950.0 } }, { "tier": 8.0, "symbol": "NMR/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 8, - "initialLeverage": 4, - "notionalCap": 1500000, - "notionalFloor": 750000, - "maintMarginRatio": 0.125, - "cum": 35327.5 + "initialLeverage": 3, + "notionalCap": 1000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.1667, + "cum": 34800.0 } }, { "tier": 9.0, "symbol": "NMR/USDT:USDT", "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 4500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 9, - "initialLeverage": 3, - "notionalCap": 4500000, - "notionalFloor": 1500000, - "maintMarginRatio": 0.1667, - "cum": 97877.5 - } - }, - { - "tier": 10.0, - "symbol": "NMR/USDT:USDT", - "currency": "USDT", - "minNotional": 4500000.0, + "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 10, + "bracket": 9, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 4500000, + "notionalFloor": 1000000, "maintMarginRatio": 0.25, - "cum": 472727.5 + "cum": 118100.0 } }, { - "tier": 11.0, + "tier": 10.0, "symbol": "NMR/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -62880,12 +62831,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 11, + "bracket": 10, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 2347727.5 + "cum": 1993100.0 } } ], @@ -71101,13 +71052,13 @@ "symbol": "PROVE/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 50000.0, + "maxNotional": 25000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": 3, "initialLeverage": 25, - "notionalCap": 50000, + "notionalCap": 25000, "notionalFloor": 10000, "maintMarginRatio": 0.02, "cum": 75.0 @@ -71117,51 +71068,51 @@ "tier": 4.0, "symbol": "PROVE/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, + "minNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": 4, "initialLeverage": 20, - "notionalCap": 100000, - "notionalFloor": 50000, + "notionalCap": 50000, + "notionalFloor": 25000, "maintMarginRatio": 0.025, - "cum": 325.0 + "cum": 200.0 } }, { "tier": 5.0, "symbol": "PROVE/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 175000.0, - "maintenanceMarginRate": 0.0333, - "maxLeverage": 15.0, + "minNotional": 50000.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 5, - "initialLeverage": 15, - "notionalCap": 175000, - "notionalFloor": 100000, - "maintMarginRatio": 0.0333, - "cum": 1155.0 + "initialLeverage": 10, + "notionalCap": 125000, + "notionalFloor": 50000, + "maintMarginRatio": 0.05, + "cum": 1450.0 } }, { "tier": 6.0, "symbol": "PROVE/USDT:USDT", "currency": "USDT", - "minNotional": 175000.0, + "minNotional": 125000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 6, - "initialLeverage": 10, + "initialLeverage": 5, "notionalCap": 250000, - "notionalFloor": 175000, - "maintMarginRatio": 0.05, - "cum": 4077.5 + "notionalFloor": 125000, + "maintMarginRatio": 0.1, + "cum": 7700.0 } }, { @@ -71169,71 +71120,54 @@ "symbol": "PROVE/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 7, - "initialLeverage": 5, - "notionalCap": 750000, + "initialLeverage": 4, + "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1, - "cum": 16577.5 + "maintMarginRatio": 0.125, + "cum": 13950.0 } }, { "tier": 8.0, "symbol": "PROVE/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 8, - "initialLeverage": 4, - "notionalCap": 1500000, - "notionalFloor": 750000, - "maintMarginRatio": 0.125, - "cum": 35327.5 + "initialLeverage": 3, + "notionalCap": 1000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.1667, + "cum": 34800.0 } }, { "tier": 9.0, "symbol": "PROVE/USDT:USDT", "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 4500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 9, - "initialLeverage": 3, - "notionalCap": 4500000, - "notionalFloor": 1500000, - "maintMarginRatio": 0.1667, - "cum": 97877.5 - } - }, - { - "tier": 10.0, - "symbol": "PROVE/USDT:USDT", - "currency": "USDT", - "minNotional": 4500000.0, + "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 10, + "bracket": 9, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 4500000, + "notionalFloor": 1000000, "maintMarginRatio": 0.25, - "cum": 472727.5 + "cum": 118100.0 } }, { - "tier": 11.0, + "tier": 10.0, "symbol": "PROVE/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -71241,12 +71175,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 11, + "bracket": 10, "initialLeverage": 1, "notionalCap": 12000000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 2347727.5 + "cum": 1993100.0 } } ], @@ -79965,13 +79899,13 @@ "symbol": "SNX/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 50000.0, + "maxNotional": 25000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": 3, "initialLeverage": 25, - "notionalCap": 50000, + "notionalCap": 25000, "notionalFloor": 10000, "maintMarginRatio": 0.02, "cum": 75.0 @@ -79981,51 +79915,51 @@ "tier": 4.0, "symbol": "SNX/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, + "minNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": 4, "initialLeverage": 20, - "notionalCap": 100000, - "notionalFloor": 50000, + "notionalCap": 50000, + "notionalFloor": 25000, "maintMarginRatio": 0.025, - "cum": 325.0 + "cum": 200.0 } }, { "tier": 5.0, "symbol": "SNX/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 175000.0, - "maintenanceMarginRate": 0.0333, - "maxLeverage": 15.0, + "minNotional": 50000.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 5, - "initialLeverage": 15, - "notionalCap": 175000, - "notionalFloor": 100000, - "maintMarginRatio": 0.0333, - "cum": 1155.0 + "initialLeverage": 10, + "notionalCap": 125000, + "notionalFloor": 50000, + "maintMarginRatio": 0.05, + "cum": 1450.0 } }, { "tier": 6.0, "symbol": "SNX/USDT:USDT", "currency": "USDT", - "minNotional": 175000.0, + "minNotional": 125000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 6, - "initialLeverage": 10, + "initialLeverage": 5, "notionalCap": 250000, - "notionalFloor": 175000, - "maintMarginRatio": 0.05, - "cum": 4077.5 + "notionalFloor": 125000, + "maintMarginRatio": 0.1, + "cum": 7700.0 } }, { @@ -80033,71 +79967,54 @@ "symbol": "SNX/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 7, - "initialLeverage": 5, - "notionalCap": 750000, + "initialLeverage": 4, + "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1, - "cum": 16577.5 + "maintMarginRatio": 0.125, + "cum": 13950.0 } }, { "tier": 8.0, "symbol": "SNX/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 8, - "initialLeverage": 4, - "notionalCap": 1500000, - "notionalFloor": 750000, - "maintMarginRatio": 0.125, - "cum": 35327.5 + "initialLeverage": 3, + "notionalCap": 1000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.1667, + "cum": 34800.0 } }, { "tier": 9.0, "symbol": "SNX/USDT:USDT", "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 4500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 9, - "initialLeverage": 3, - "notionalCap": 4500000, - "notionalFloor": 1500000, - "maintMarginRatio": 0.1667, - "cum": 97877.5 - } - }, - { - "tier": 10.0, - "symbol": "SNX/USDT:USDT", - "currency": "USDT", - "minNotional": 4500000.0, + "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 10, + "bracket": 9, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 4500000, + "notionalFloor": 1000000, "maintMarginRatio": 0.25, - "cum": 472727.5 + "cum": 118100.0 } }, { - "tier": 11.0, + "tier": 10.0, "symbol": "SNX/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -80105,12 +80022,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 11, + "bracket": 10, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 2347727.5 + "cum": 1993100.0 } } ], @@ -81448,14 +81365,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -81465,14 +81382,14 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 25, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.02, "cum": 25.0 } }, @@ -81481,15 +81398,15 @@ "symbol": "SPK/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 50000, + "initialLeverage": 20, + "notionalCap": 25000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, + "maintMarginRatio": 0.025, "cum": 75.0 } }, @@ -81497,51 +81414,51 @@ "tier": 4.0, "symbol": "SPK/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 25000.0, + "maxNotional": 62500.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 4, - "initialLeverage": 20, - "notionalCap": 100000, - "notionalFloor": 50000, - "maintMarginRatio": 0.025, - "cum": 325.0 + "initialLeverage": 10, + "notionalCap": 62500, + "notionalFloor": 25000, + "maintMarginRatio": 0.05, + "cum": 700.0 } }, { "tier": 5.0, "symbol": "SPK/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 175000.0, - "maintenanceMarginRate": 0.0333, - "maxLeverage": 15.0, + "minNotional": 62500.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 5, - "initialLeverage": 15, - "notionalCap": 175000, - "notionalFloor": 100000, - "maintMarginRatio": 0.0333, - "cum": 1155.0 + "initialLeverage": 5, + "notionalCap": 125000, + "notionalFloor": 62500, + "maintMarginRatio": 0.1, + "cum": 3825.0 } }, { "tier": 6.0, "symbol": "SPK/USDT:USDT", "currency": "USDT", - "minNotional": 175000.0, + "minNotional": 125000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 6, - "initialLeverage": 10, + "initialLeverage": 4, "notionalCap": 250000, - "notionalFloor": 175000, - "maintMarginRatio": 0.05, - "cum": 4077.5 + "notionalFloor": 125000, + "maintMarginRatio": 0.125, + "cum": 6950.0 } }, { @@ -81549,71 +81466,37 @@ "symbol": "SPK/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 7, - "initialLeverage": 5, - "notionalCap": 750000, + "initialLeverage": 3, + "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1, - "cum": 16577.5 + "maintMarginRatio": 0.1667, + "cum": 17375.0 } }, { "tier": 8.0, "symbol": "SPK/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 8, - "initialLeverage": 4, - "notionalCap": 1500000, - "notionalFloor": 750000, - "maintMarginRatio": 0.125, - "cum": 35327.5 - } - }, - { - "tier": 9.0, - "symbol": "SPK/USDT:USDT", - "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 4500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 9, - "initialLeverage": 3, - "notionalCap": 4500000, - "notionalFloor": 1500000, - "maintMarginRatio": 0.1667, - "cum": 97877.5 - } - }, - { - "tier": 10.0, - "symbol": "SPK/USDT:USDT", - "currency": "USDT", - "minNotional": 4500000.0, + "minNotional": 500000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 4500000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 472727.5 + "cum": 59025.0 } }, { - "tier": 11.0, + "tier": 9.0, "symbol": "SPK/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -81621,12 +81504,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 11, + "bracket": 9, "initialLeverage": 1, "notionalCap": 12000000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 2347727.5 + "cum": 1934025.0 } } ], @@ -84600,13 +84483,13 @@ "symbol": "SUSHI/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 50000.0, + "maxNotional": 25000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": 3, "initialLeverage": 25, - "notionalCap": 50000, + "notionalCap": 25000, "notionalFloor": 10000, "maintMarginRatio": 0.02, "cum": 75.0 @@ -84616,51 +84499,51 @@ "tier": 4.0, "symbol": "SUSHI/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, + "minNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": 4, "initialLeverage": 20, - "notionalCap": 100000, - "notionalFloor": 50000, + "notionalCap": 50000, + "notionalFloor": 25000, "maintMarginRatio": 0.025, - "cum": 325.0 + "cum": 200.0 } }, { "tier": 5.0, "symbol": "SUSHI/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 175000.0, - "maintenanceMarginRate": 0.0333, - "maxLeverage": 15.0, + "minNotional": 50000.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 5, - "initialLeverage": 15, - "notionalCap": 175000, - "notionalFloor": 100000, - "maintMarginRatio": 0.0333, - "cum": 1155.0 + "initialLeverage": 10, + "notionalCap": 125000, + "notionalFloor": 50000, + "maintMarginRatio": 0.05, + "cum": 1450.0 } }, { "tier": 6.0, "symbol": "SUSHI/USDT:USDT", "currency": "USDT", - "minNotional": 175000.0, + "minNotional": 125000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 6, - "initialLeverage": 10, + "initialLeverage": 5, "notionalCap": 250000, - "notionalFloor": 175000, - "maintMarginRatio": 0.05, - "cum": 4077.5 + "notionalFloor": 125000, + "maintMarginRatio": 0.1, + "cum": 7700.0 } }, { @@ -84668,71 +84551,54 @@ "symbol": "SUSHI/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 7, - "initialLeverage": 5, - "notionalCap": 750000, + "initialLeverage": 4, + "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1, - "cum": 16577.5 + "maintMarginRatio": 0.125, + "cum": 13950.0 } }, { "tier": 8.0, "symbol": "SUSHI/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 8, - "initialLeverage": 4, - "notionalCap": 1500000, - "notionalFloor": 750000, - "maintMarginRatio": 0.125, - "cum": 35327.5 + "initialLeverage": 3, + "notionalCap": 1000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.1667, + "cum": 34800.0 } }, { "tier": 9.0, "symbol": "SUSHI/USDT:USDT", "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 4500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 9, - "initialLeverage": 3, - "notionalCap": 4500000, - "notionalFloor": 1500000, - "maintMarginRatio": 0.1667, - "cum": 97877.5 - } - }, - { - "tier": 10.0, - "symbol": "SUSHI/USDT:USDT", - "currency": "USDT", - "minNotional": 4500000.0, + "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 10, + "bracket": 9, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 4500000, + "notionalFloor": 1000000, "maintMarginRatio": 0.25, - "cum": 472727.5 + "cum": 118100.0 } }, { - "tier": 11.0, + "tier": 10.0, "symbol": "SUSHI/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -84740,12 +84606,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 11, + "bracket": 10, "initialLeverage": 1, "notionalCap": 12000000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 2347727.5 + "cum": 1993100.0 } } ], @@ -88070,14 +87936,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 40.0, + "maintenanceMarginRate": 0.04, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 40, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.02, + "maintMarginRatio": 0.04, "cum": 0.0 } }, @@ -88087,15 +87953,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 2, - "initialLeverage": 20, + "initialLeverage": 10, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.025, - "cum": 25.0 + "maintMarginRatio": 0.05, + "cum": 50.0 } }, { @@ -88103,37 +87969,20 @@ "symbol": "TRADOOR/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": 3, - "initialLeverage": 10, - "notionalCap": 20000, - "notionalFloor": 10000, - "maintMarginRatio": 0.05, - "cum": 275.0 - } - }, - { - "tier": 4.0, - "symbol": "TRADOOR/USDT:USDT", - "currency": "USDT", - "minNotional": 20000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": 4, + "bracket": 3, "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 20000, + "notionalFloor": 10000, "maintMarginRatio": 0.1, - "cum": 1275.0 + "cum": 550.0 } }, { - "tier": 5.0, + "tier": 4.0, "symbol": "TRADOOR/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, @@ -88141,63 +87990,63 @@ "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 5, + "bracket": 4, "initialLeverage": 4, "notionalCap": 100000, "notionalFloor": 50000, "maintMarginRatio": 0.125, - "cum": 2525.0 + "cum": 1800.0 + } + }, + { + "tier": 5.0, + "symbol": "TRADOOR/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 5, + "initialLeverage": 3, + "notionalCap": 200000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 5970.0 } }, { "tier": 6.0, "symbol": "TRADOOR/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, + "minNotional": 200000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 6, - "initialLeverage": 3, - "notionalCap": 250000, - "notionalFloor": 100000, - "maintMarginRatio": 0.1667, - "cum": 6695.0 + "initialLeverage": 2, + "notionalCap": 1000000, + "notionalFloor": 200000, + "maintMarginRatio": 0.25, + "cum": 22630.0 } }, { "tier": 7.0, "symbol": "TRADOOR/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 2500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 2500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.25, - "cum": 27520.0 - } - }, - { - "tier": 8.0, - "symbol": "TRADOOR/USDT:USDT", - "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 1000000.0, + "maxNotional": 1200000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 7, "initialLeverage": 1, - "notionalCap": 5000000, - "notionalFloor": 2500000, + "notionalCap": 1200000, + "notionalFloor": 1000000, "maintMarginRatio": 0.5, - "cum": 652520.0 + "cum": 272630.0 } } ], @@ -88208,14 +88057,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -88225,14 +88074,14 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 25, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.02, "cum": 25.0 } }, @@ -88241,15 +88090,15 @@ "symbol": "TRB/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 50000, + "initialLeverage": 20, + "notionalCap": 25000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, + "maintMarginRatio": 0.025, "cum": 75.0 } }, @@ -88257,51 +88106,51 @@ "tier": 4.0, "symbol": "TRB/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 25000.0, + "maxNotional": 62500.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 4, - "initialLeverage": 20, - "notionalCap": 100000, - "notionalFloor": 50000, - "maintMarginRatio": 0.025, - "cum": 325.0 + "initialLeverage": 10, + "notionalCap": 62500, + "notionalFloor": 25000, + "maintMarginRatio": 0.05, + "cum": 700.0 } }, { "tier": 5.0, "symbol": "TRB/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 175000.0, - "maintenanceMarginRate": 0.0333, - "maxLeverage": 15.0, + "minNotional": 62500.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 5, - "initialLeverage": 15, - "notionalCap": 175000, - "notionalFloor": 100000, - "maintMarginRatio": 0.0333, - "cum": 1155.0 + "initialLeverage": 5, + "notionalCap": 125000, + "notionalFloor": 62500, + "maintMarginRatio": 0.1, + "cum": 3825.0 } }, { "tier": 6.0, "symbol": "TRB/USDT:USDT", "currency": "USDT", - "minNotional": 175000.0, + "minNotional": 125000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 6, - "initialLeverage": 10, + "initialLeverage": 4, "notionalCap": 250000, - "notionalFloor": 175000, - "maintMarginRatio": 0.05, - "cum": 4077.5 + "notionalFloor": 125000, + "maintMarginRatio": 0.125, + "cum": 6950.0 } }, { @@ -88309,71 +88158,37 @@ "symbol": "TRB/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 7, - "initialLeverage": 5, - "notionalCap": 750000, + "initialLeverage": 3, + "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1, - "cum": 16577.5 + "maintMarginRatio": 0.1667, + "cum": 17375.0 } }, { "tier": 8.0, "symbol": "TRB/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 1500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 8, - "initialLeverage": 4, - "notionalCap": 1500000, - "notionalFloor": 750000, - "maintMarginRatio": 0.125, - "cum": 35327.5 - } - }, - { - "tier": 9.0, - "symbol": "TRB/USDT:USDT", - "currency": "USDT", - "minNotional": 1500000.0, - "maxNotional": 4500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 9, - "initialLeverage": 3, - "notionalCap": 4500000, - "notionalFloor": 1500000, - "maintMarginRatio": 0.1667, - "cum": 97877.5 - } - }, - { - "tier": 10.0, - "symbol": "TRB/USDT:USDT", - "currency": "USDT", - "minNotional": 4500000.0, + "minNotional": 500000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 4500000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 472727.5 + "cum": 59025.0 } }, { - "tier": 11.0, + "tier": 9.0, "symbol": "TRB/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -88381,12 +88196,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 11, + "bracket": 9, "initialLeverage": 1, "notionalCap": 12000000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 2347727.5 + "cum": 1934025.0 } } ], @@ -94105,13 +93920,13 @@ "symbol": "W/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 7500.0, + "maxNotional": 5000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 75.0, "info": { "bracket": 1, "initialLeverage": 75, - "notionalCap": 7500, + "notionalCap": 5000, "notionalFloor": 0, "maintMarginRatio": 0.01, "cum": 0.0 @@ -94121,136 +93936,136 @@ "tier": 2.0, "symbol": "W/USDT:USDT", "currency": "USDT", - "minNotional": 7500.0, - "maxNotional": 15000.0, + "minNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": 2, "initialLeverage": 50, - "notionalCap": 15000, - "notionalFloor": 7500, + "notionalCap": 10000, + "notionalFloor": 5000, "maintMarginRatio": 0.015, - "cum": 37.5 + "cum": 25.0 } }, { "tier": 3.0, "symbol": "W/USDT:USDT", "currency": "USDT", - "minNotional": 15000.0, - "maxNotional": 75000.0, + "minNotional": 10000.0, + "maxNotional": 25000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": 3, "initialLeverage": 25, - "notionalCap": 75000, - "notionalFloor": 15000, + "notionalCap": 25000, + "notionalFloor": 10000, "maintMarginRatio": 0.02, - "cum": 112.5 + "cum": 75.0 } }, { "tier": 4.0, "symbol": "W/USDT:USDT", "currency": "USDT", - "minNotional": 75000.0, - "maxNotional": 200000.0, + "minNotional": 25000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": 4, "initialLeverage": 20, - "notionalCap": 200000, - "notionalFloor": 75000, + "notionalCap": 50000, + "notionalFloor": 25000, "maintMarginRatio": 0.025, - "cum": 487.5 + "cum": 200.0 } }, { "tier": 5.0, "symbol": "W/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 350000.0, - "maintenanceMarginRate": 0.0333, - "maxLeverage": 15.0, + "minNotional": 50000.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 5, - "initialLeverage": 15, - "notionalCap": 350000, - "notionalFloor": 200000, - "maintMarginRatio": 0.0333, - "cum": 2147.5 + "initialLeverage": 10, + "notionalCap": 125000, + "notionalFloor": 50000, + "maintMarginRatio": 0.05, + "cum": 1450.0 } }, { "tier": 6.0, "symbol": "W/USDT:USDT", "currency": "USDT", - "minNotional": 350000.0, - "maxNotional": 750000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 125000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 6, - "initialLeverage": 10, - "notionalCap": 750000, - "notionalFloor": 350000, - "maintMarginRatio": 0.05, - "cum": 7992.5 + "initialLeverage": 5, + "notionalCap": 250000, + "notionalFloor": 125000, + "maintMarginRatio": 0.1, + "cum": 7700.0 } }, { "tier": 7.0, "symbol": "W/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 3000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 7, - "initialLeverage": 5, - "notionalCap": 3000000, - "notionalFloor": 750000, - "maintMarginRatio": 0.1, - "cum": 45492.5 + "initialLeverage": 4, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.125, + "cum": 13950.0 } }, { "tier": 8.0, "symbol": "W/USDT:USDT", "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 4500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 500000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 8, - "initialLeverage": 4, - "notionalCap": 4500000, - "notionalFloor": 3000000, - "maintMarginRatio": 0.125, - "cum": 120492.5 + "initialLeverage": 3, + "notionalCap": 1000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.1667, + "cum": 34800.0 } }, { "tier": 9.0, "symbol": "W/USDT:USDT", "currency": "USDT", - "minNotional": 4500000.0, + "minNotional": 1000000.0, "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 9, - "initialLeverage": 3, + "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 4500000, - "maintMarginRatio": 0.1667, - "cum": 308142.5 + "notionalFloor": 1000000, + "maintMarginRatio": 0.25, + "cum": 118100.0 } }, { @@ -94258,33 +94073,16 @@ "symbol": "W/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, - "maxNotional": 12000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 10, - "initialLeverage": 2, - "notionalCap": 12000000, - "notionalFloor": 7500000, - "maintMarginRatio": 0.25, - "cum": 932892.5 - } - }, - { - "tier": 11.0, - "symbol": "W/USDT:USDT", - "currency": "USDT", - "minNotional": 12000000.0, - "maxNotional": 18000000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 11, + "bracket": 10, "initialLeverage": 1, - "notionalCap": 18000000, - "notionalFloor": 12000000, + "notionalCap": 12500000, + "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 3932892.5 + "cum": 1993100.0 } } ], @@ -95156,13 +94954,13 @@ "symbol": "WIF/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 20000.0, + "maxNotional": 5000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 75.0, "info": { "bracket": 1, "initialLeverage": 75, - "notionalCap": 20000, + "notionalCap": 5000, "notionalFloor": 0, "maintMarginRatio": 0.01, "cum": 0.0 @@ -95172,136 +94970,170 @@ "tier": 2.0, "symbol": "WIF/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 200000.0, + "minNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": 2, "initialLeverage": 50, - "notionalCap": 200000, - "notionalFloor": 20000, + "notionalCap": 10000, + "notionalFloor": 5000, "maintMarginRatio": 0.015, - "cum": 100.0 + "cum": 25.0 } }, { "tier": 3.0, "symbol": "WIF/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 1000000.0, + "minNotional": 10000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": 3, "initialLeverage": 25, - "notionalCap": 1000000, - "notionalFloor": 200000, + "notionalCap": 50000, + "notionalFloor": 10000, "maintMarginRatio": 0.02, - "cum": 1100.0 + "cum": 75.0 } }, { "tier": 4.0, "symbol": "WIF/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": 4, "initialLeverage": 20, - "notionalCap": 2000000, - "notionalFloor": 1000000, + "notionalCap": 100000, + "notionalFloor": 50000, "maintMarginRatio": 0.025, - "cum": 6100.0 + "cum": 325.0 } }, { "tier": 5.0, "symbol": "WIF/USDT:USDT", "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 100000.0, + "maxNotional": 175000.0, + "maintenanceMarginRate": 0.0333, + "maxLeverage": 15.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 5000000, - "notionalFloor": 2000000, - "maintMarginRatio": 0.05, - "cum": 56100.0 + "initialLeverage": 15, + "notionalCap": 175000, + "notionalFloor": 100000, + "maintMarginRatio": 0.0333, + "cum": 1155.0 } }, { "tier": 6.0, "symbol": "WIF/USDT:USDT", "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 175000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 7500000, - "notionalFloor": 5000000, - "maintMarginRatio": 0.1, - "cum": 306100.0 + "initialLeverage": 10, + "notionalCap": 250000, + "notionalFloor": 175000, + "maintMarginRatio": 0.05, + "cum": 4077.5 } }, { "tier": 7.0, "symbol": "WIF/USDT:USDT", "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 250000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 7, - "initialLeverage": 4, - "notionalCap": 10000000, - "notionalFloor": 7500000, - "maintMarginRatio": 0.125, - "cum": 493600.0 + "initialLeverage": 5, + "notionalCap": 750000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1, + "cum": 16577.5 } }, { "tier": 8.0, "symbol": "WIF/USDT:USDT", "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 12500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 750000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 8, - "initialLeverage": 2, - "notionalCap": 12500000, - "notionalFloor": 10000000, - "maintMarginRatio": 0.25, - "cum": 1743600.0 + "initialLeverage": 4, + "notionalCap": 1500000, + "notionalFloor": 750000, + "maintMarginRatio": 0.125, + "cum": 35327.5 } }, { "tier": 9.0, "symbol": "WIF/USDT:USDT", "currency": "USDT", - "minNotional": 12500000.0, - "maxNotional": 15000000.0, + "minNotional": 1500000.0, + "maxNotional": 4500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 9, + "initialLeverage": 3, + "notionalCap": 4500000, + "notionalFloor": 1500000, + "maintMarginRatio": 0.1667, + "cum": 97877.5 + } + }, + { + "tier": 10.0, + "symbol": "WIF/USDT:USDT", + "currency": "USDT", + "minNotional": 4500000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 10, + "initialLeverage": 2, + "notionalCap": 7500000, + "notionalFloor": 4500000, + "maintMarginRatio": 0.25, + "cum": 472727.5 + } + }, + { + "tier": 11.0, + "symbol": "WIF/USDT:USDT", + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 9, + "bracket": 11, "initialLeverage": 1, - "notionalCap": 15000000, - "notionalFloor": 12500000, + "notionalCap": 12500000, + "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 4868600.0 + "cum": 2347727.5 } } ], @@ -96758,14 +96590,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.01, + "maxLeverage": 75.0, "info": { "bracket": 1, - "initialLeverage": 50, + "initialLeverage": 75, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.01, "cum": 0.0 } }, @@ -96775,14 +96607,14 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 2, - "initialLeverage": 25, + "initialLeverage": 50, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.02, + "maintMarginRatio": 0.015, "cum": 25.0 } }, @@ -96791,15 +96623,15 @@ "symbol": "XAUT/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, "info": { "bracket": 3, - "initialLeverage": 20, - "notionalCap": 25000, + "initialLeverage": 25, + "notionalCap": 50000, "notionalFloor": 10000, - "maintMarginRatio": 0.025, + "maintMarginRatio": 0.02, "cum": 75.0 } }, @@ -96807,102 +96639,136 @@ "tier": 4.0, "symbol": "XAUT/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 62500.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 4, - "initialLeverage": 10, - "notionalCap": 62500, - "notionalFloor": 25000, - "maintMarginRatio": 0.05, - "cum": 700.0 + "initialLeverage": 20, + "notionalCap": 100000, + "notionalFloor": 50000, + "maintMarginRatio": 0.025, + "cum": 325.0 } }, { "tier": 5.0, "symbol": "XAUT/USDT:USDT", "currency": "USDT", - "minNotional": 62500.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 100000.0, + "maxNotional": 175000.0, + "maintenanceMarginRate": 0.03333, + "maxLeverage": 15.0, "info": { "bracket": 5, - "initialLeverage": 5, - "notionalCap": 125000, - "notionalFloor": 62500, - "maintMarginRatio": 0.1, - "cum": 3825.0 + "initialLeverage": 15, + "notionalCap": 175000, + "notionalFloor": 100000, + "maintMarginRatio": 0.03333, + "cum": 1158.0 } }, { "tier": 6.0, "symbol": "XAUT/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 175000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 6, - "initialLeverage": 4, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.125, - "cum": 6950.0 + "initialLeverage": 10, + "notionalCap": 500000, + "notionalFloor": 175000, + "maintMarginRatio": 0.05, + "cum": 4075.25 } }, { "tier": 7.0, "symbol": "XAUT/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, + "minNotional": 500000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 7, - "initialLeverage": 3, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.1667, - "cum": 17375.0 + "initialLeverage": 5, + "notionalCap": 750000, + "notionalFloor": 500000, + "maintMarginRatio": 0.1, + "cum": 29075.25 } }, { "tier": 8.0, "symbol": "XAUT/USDT:USDT", "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 750000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 8, - "initialLeverage": 2, - "notionalCap": 7500000, - "notionalFloor": 500000, - "maintMarginRatio": 0.25, - "cum": 59025.0 + "initialLeverage": 4, + "notionalCap": 1500000, + "notionalFloor": 750000, + "maintMarginRatio": 0.125, + "cum": 47825.25 } }, { "tier": 9.0, "symbol": "XAUT/USDT:USDT", "currency": "USDT", + "minNotional": 1500000.0, + "maxNotional": 4500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 9, + "initialLeverage": 3, + "notionalCap": 4500000, + "notionalFloor": 1500000, + "maintMarginRatio": 0.1667, + "cum": 110375.25 + } + }, + { + "tier": 10.0, + "symbol": "XAUT/USDT:USDT", + "currency": "USDT", + "minNotional": 4500000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 10, + "initialLeverage": 2, + "notionalCap": 7500000, + "notionalFloor": 4500000, + "maintMarginRatio": 0.25, + "cum": 485225.25 + } + }, + { + "tier": 11.0, + "symbol": "XAUT/USDT:USDT", + "currency": "USDT", "minNotional": 7500000.0, "maxNotional": 12500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 9, + "bracket": 11, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 1934025.0 + "cum": 2360225.25 } } ], @@ -97895,13 +97761,13 @@ "symbol": "XPL/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 20000.0, + "maxNotional": 5000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 75.0, "info": { "bracket": 1, "initialLeverage": 75, - "notionalCap": 20000, + "notionalCap": 5000, "notionalFloor": 0, "maintMarginRatio": 0.01, "cum": 0.0 @@ -97911,136 +97777,170 @@ "tier": 2.0, "symbol": "XPL/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 200000.0, + "minNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": 2, "initialLeverage": 50, - "notionalCap": 200000, - "notionalFloor": 20000, + "notionalCap": 10000, + "notionalFloor": 5000, "maintMarginRatio": 0.015, - "cum": 100.0 + "cum": 25.0 } }, { "tier": 3.0, "symbol": "XPL/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 1000000.0, + "minNotional": 10000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": 3, "initialLeverage": 25, - "notionalCap": 1000000, - "notionalFloor": 200000, + "notionalCap": 50000, + "notionalFloor": 10000, "maintMarginRatio": 0.02, - "cum": 1100.0 + "cum": 75.0 } }, { "tier": 4.0, "symbol": "XPL/USDT:USDT", "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 2000000.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": 4, "initialLeverage": 20, - "notionalCap": 2000000, - "notionalFloor": 1000000, + "notionalCap": 100000, + "notionalFloor": 50000, "maintMarginRatio": 0.025, - "cum": 6100.0 + "cum": 325.0 } }, { "tier": 5.0, "symbol": "XPL/USDT:USDT", "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 5000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 100000.0, + "maxNotional": 175000.0, + "maintenanceMarginRate": 0.0333, + "maxLeverage": 15.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 5000000, - "notionalFloor": 2000000, - "maintMarginRatio": 0.05, - "cum": 56100.0 + "initialLeverage": 15, + "notionalCap": 175000, + "notionalFloor": 100000, + "maintMarginRatio": 0.0333, + "cum": 1155.0 } }, { "tier": 6.0, "symbol": "XPL/USDT:USDT", "currency": "USDT", - "minNotional": 5000000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 175000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 7500000, - "notionalFloor": 5000000, - "maintMarginRatio": 0.1, - "cum": 306100.0 + "initialLeverage": 10, + "notionalCap": 250000, + "notionalFloor": 175000, + "maintMarginRatio": 0.05, + "cum": 4077.5 } }, { "tier": 7.0, "symbol": "XPL/USDT:USDT", "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 250000.0, + "maxNotional": 750000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 7, - "initialLeverage": 4, - "notionalCap": 10000000, - "notionalFloor": 7500000, - "maintMarginRatio": 0.125, - "cum": 493600.0 + "initialLeverage": 5, + "notionalCap": 750000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1, + "cum": 16577.5 } }, { "tier": 8.0, "symbol": "XPL/USDT:USDT", "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 12500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 750000.0, + "maxNotional": 1500000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 8, - "initialLeverage": 2, - "notionalCap": 12500000, - "notionalFloor": 10000000, - "maintMarginRatio": 0.25, - "cum": 1743600.0 + "initialLeverage": 4, + "notionalCap": 1500000, + "notionalFloor": 750000, + "maintMarginRatio": 0.125, + "cum": 35327.5 } }, { "tier": 9.0, "symbol": "XPL/USDT:USDT", "currency": "USDT", - "minNotional": 12500000.0, - "maxNotional": 15000000.0, + "minNotional": 1500000.0, + "maxNotional": 4500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 9, + "initialLeverage": 3, + "notionalCap": 4500000, + "notionalFloor": 1500000, + "maintMarginRatio": 0.1667, + "cum": 97877.5 + } + }, + { + "tier": 10.0, + "symbol": "XPL/USDT:USDT", + "currency": "USDT", + "minNotional": 4500000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 10, + "initialLeverage": 2, + "notionalCap": 7500000, + "notionalFloor": 4500000, + "maintMarginRatio": 0.25, + "cum": 472727.5 + } + }, + { + "tier": 11.0, + "symbol": "XPL/USDT:USDT", + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 9, + "bracket": 11, "initialLeverage": 1, - "notionalCap": 15000000, - "notionalFloor": 12500000, + "notionalCap": 12500000, + "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 4868600.0 + "cum": 2347727.5 } } ], @@ -100254,13 +100154,13 @@ "symbol": "ZEN/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 7500.0, + "maxNotional": 5000.0, "maintenanceMarginRate": 0.01, "maxLeverage": 75.0, "info": { "bracket": 1, "initialLeverage": 75, - "notionalCap": 7500, + "notionalCap": 5000, "notionalFloor": 0, "maintMarginRatio": 0.01, "cum": 0.0 @@ -100270,170 +100170,170 @@ "tier": 2.0, "symbol": "ZEN/USDT:USDT", "currency": "USDT", - "minNotional": 7500.0, - "maxNotional": 15000.0, + "minNotional": 5000.0, + "maxNotional": 10000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { "bracket": 2, "initialLeverage": 50, - "notionalCap": 15000, - "notionalFloor": 7500, + "notionalCap": 10000, + "notionalFloor": 5000, "maintMarginRatio": 0.015, - "cum": 37.5 + "cum": 25.0 } }, { "tier": 3.0, "symbol": "ZEN/USDT:USDT", "currency": "USDT", - "minNotional": 15000.0, - "maxNotional": 75000.0, + "minNotional": 10000.0, + "maxNotional": 50000.0, "maintenanceMarginRate": 0.02, "maxLeverage": 25.0, "info": { "bracket": 3, "initialLeverage": 25, - "notionalCap": 75000, - "notionalFloor": 15000, + "notionalCap": 50000, + "notionalFloor": 10000, "maintMarginRatio": 0.02, - "cum": 112.5 + "cum": 75.0 } }, { "tier": 4.0, "symbol": "ZEN/USDT:USDT", "currency": "USDT", - "minNotional": 75000.0, - "maxNotional": 200000.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, "maintenanceMarginRate": 0.025, "maxLeverage": 20.0, "info": { "bracket": 4, "initialLeverage": 20, - "notionalCap": 200000, - "notionalFloor": 75000, + "notionalCap": 100000, + "notionalFloor": 50000, "maintMarginRatio": 0.025, - "cum": 487.5 + "cum": 325.0 } }, { "tier": 5.0, "symbol": "ZEN/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 350000.0, + "minNotional": 100000.0, + "maxNotional": 175000.0, "maintenanceMarginRate": 0.0333, "maxLeverage": 15.0, "info": { "bracket": 5, "initialLeverage": 15, - "notionalCap": 350000, - "notionalFloor": 200000, + "notionalCap": 175000, + "notionalFloor": 100000, "maintMarginRatio": 0.0333, - "cum": 2147.5 + "cum": 1155.0 } }, { "tier": 6.0, "symbol": "ZEN/USDT:USDT", "currency": "USDT", - "minNotional": 350000.0, - "maxNotional": 750000.0, + "minNotional": 175000.0, + "maxNotional": 250000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, "info": { "bracket": 6, "initialLeverage": 10, - "notionalCap": 750000, - "notionalFloor": 350000, + "notionalCap": 250000, + "notionalFloor": 175000, "maintMarginRatio": 0.05, - "cum": 7992.5 + "cum": 4077.5 } }, { "tier": 7.0, "symbol": "ZEN/USDT:USDT", "currency": "USDT", - "minNotional": 750000.0, - "maxNotional": 3000000.0, + "minNotional": 250000.0, + "maxNotional": 750000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { "bracket": 7, "initialLeverage": 5, - "notionalCap": 3000000, - "notionalFloor": 750000, + "notionalCap": 750000, + "notionalFloor": 250000, "maintMarginRatio": 0.1, - "cum": 45492.5 + "cum": 16577.5 } }, { "tier": 8.0, "symbol": "ZEN/USDT:USDT", "currency": "USDT", - "minNotional": 3000000.0, - "maxNotional": 4500000.0, + "minNotional": 750000.0, + "maxNotional": 1500000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { "bracket": 8, "initialLeverage": 4, - "notionalCap": 4500000, - "notionalFloor": 3000000, + "notionalCap": 1500000, + "notionalFloor": 750000, "maintMarginRatio": 0.125, - "cum": 120492.5 + "cum": 35327.5 } }, { "tier": 9.0, "symbol": "ZEN/USDT:USDT", "currency": "USDT", - "minNotional": 4500000.0, - "maxNotional": 7500000.0, + "minNotional": 1500000.0, + "maxNotional": 4500000.0, "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { "bracket": 9, "initialLeverage": 3, - "notionalCap": 7500000, - "notionalFloor": 4500000, + "notionalCap": 4500000, + "notionalFloor": 1500000, "maintMarginRatio": 0.1667, - "cum": 308142.5 + "cum": 97877.5 } }, { "tier": 10.0, "symbol": "ZEN/USDT:USDT", "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 12000000.0, + "minNotional": 4500000.0, + "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": 10, "initialLeverage": 2, - "notionalCap": 12000000, - "notionalFloor": 7500000, + "notionalCap": 7500000, + "notionalFloor": 4500000, "maintMarginRatio": 0.25, - "cum": 932892.5 + "cum": 472727.5 } }, { "tier": 11.0, "symbol": "ZEN/USDT:USDT", "currency": "USDT", - "minNotional": 12000000.0, - "maxNotional": 18000000.0, + "minNotional": 7500000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": 11, "initialLeverage": 1, - "notionalCap": 18000000, - "notionalFloor": 12000000, + "notionalCap": 12500000, + "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 3932892.5 + "cum": 2347727.5 } } ], @@ -102081,14 +101981,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.04, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.04, "cum": 0.0 } }, @@ -102097,135 +101997,101 @@ "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 15000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 2, - "initialLeverage": 25, - "notionalCap": 10000, + "initialLeverage": 10, + "notionalCap": 15000, "notionalFloor": 5000, - "maintMarginRatio": 0.02, - "cum": 25.0 + "maintMarginRatio": 0.05, + "cum": 50.0 } }, { "tier": 3.0, "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 15000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 3, - "initialLeverage": 20, - "notionalCap": 25000, - "notionalFloor": 10000, - "maintMarginRatio": 0.025, - "cum": 75.0 + "initialLeverage": 5, + "notionalCap": 60000, + "notionalFloor": 15000, + "maintMarginRatio": 0.1, + "cum": 800.0 } }, { "tier": 4.0, "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 62500.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 60000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 4, - "initialLeverage": 10, - "notionalCap": 62500, - "notionalFloor": 25000, - "maintMarginRatio": 0.05, - "cum": 700.0 + "initialLeverage": 4, + "notionalCap": 200000, + "notionalFloor": 60000, + "maintMarginRatio": 0.125, + "cum": 2300.0 } }, { "tier": 5.0, "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", - "minNotional": 62500.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 5, - "initialLeverage": 5, - "notionalCap": 125000, - "notionalFloor": 62500, - "maintMarginRatio": 0.1, - "cum": 3825.0 + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 200000, + "maintMarginRatio": 0.1667, + "cum": 10640.0 } }, { "tier": 6.0, "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 500000.0, + "maxNotional": 2500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 6, - "initialLeverage": 4, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.125, - "cum": 6950.0 + "initialLeverage": 2, + "notionalCap": 2500000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 52290.0 } }, { "tier": 7.0, "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 7, - "initialLeverage": 3, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.1667, - "cum": 17375.0 - } - }, - { - "tier": 8.0, - "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", - "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 8, - "initialLeverage": 2, - "notionalCap": 7500000, - "notionalFloor": 500000, - "maintMarginRatio": 0.25, - "cum": 59025.0 - } - }, - { - "tier": 9.0, - "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", - "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 12500000.0, + "minNotional": 2500000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 9, + "bracket": 7, "initialLeverage": 1, - "notionalCap": 12500000, - "notionalFloor": 7500000, + "notionalCap": 5000000, + "notionalFloor": 2500000, "maintMarginRatio": 0.5, - "cum": 1934025.0 + "cum": 677290.0 } } ], From 7e3e206ab3407a2ca07029c82ed8344fd82a8279 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 16 Apr 2026 06:35:04 +0200 Subject: [PATCH 249/315] chore(ci): move leverage tiers update by 1 hour --- .github/workflows/binance-lev-tier-update.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/binance-lev-tier-update.yml b/.github/workflows/binance-lev-tier-update.yml index 4354c233b..cc0c11c68 100644 --- a/.github/workflows/binance-lev-tier-update.yml +++ b/.github/workflows/binance-lev-tier-update.yml @@ -2,7 +2,7 @@ name: Binance Leverage tiers update on: schedule: - - cron: "25 3 * * 4" + - cron: "25 2 * * 4" # on demand workflow_dispatch: From 6e270c5d062e25a017bd1ff38739929c1d1a9b4c Mon Sep 17 00:00:00 2001 From: ABS <53243996+ABSllk@users.noreply.github.com> Date: Fri, 17 Apr 2026 00:18:45 +0800 Subject: [PATCH 250/315] fix(bitget): Use correct planType for Bitget futures stoploss cancellation --- freqtrade/exchange/bitget.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/freqtrade/exchange/bitget.py b/freqtrade/exchange/bitget.py index 9691f72f8..c769fc0c8 100644 --- a/freqtrade/exchange/bitget.py +++ b/freqtrade/exchange/bitget.py @@ -136,6 +136,15 @@ class Bitget(Exchange): return self._fetch_stop_order_fallback(order_id, pair) + def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict: + cancel_params = params.copy() if params else {} + cancel_params["stop"] = True + + if self.trading_mode != TradingMode.FUTURES: + return self.cancel_order(order_id, pair, cancel_params) + + return self.cancel_order(order_id, pair, {**cancel_params, "planType": "pos_loss"}) + @retrier def additional_exchange_init(self) -> None: """ From ee65ffc68fe051b9f0ceee8cfed6992febe1da21 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 16 Apr 2026 20:17:05 +0200 Subject: [PATCH 251/315] fix: align telegram output better --- 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 8e88cc4e7..4d3b5372c 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -483,7 +483,7 @@ class Telegram(RPCHandler): profit_prefix = "Sub " cp_extra = ( f"*Final Profit:* `{format_pct(msg['final_profit_ratio'])} " - f"({msg['cumulative_profit']:.8f} {msg['quote_currency']}{cp_fiat})`\n" + f"({fmt_coin(msg['cumulative_profit'], msg['stake_currency'])}{cp_fiat})`\n" ) else: exit_wording = f"Partially {exit_wording.lower()}" @@ -832,7 +832,7 @@ class Telegram(RPCHandler): ): # Adding initial stoploss only if it is different from stoploss lines.append( - f"*Initial Stoploss:* `{r['initial_stop_loss_abs']:.8f}` " + f"*Initial Stoploss:* `{round_value(r['initial_stop_loss_abs'], 8)}` " f"`({format_pct(r['initial_stop_loss_ratio'])})`" ) From 55361f0a9eb2bbcde743e98d39942a49df2503eb Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 19 Apr 2026 13:04:23 +0200 Subject: [PATCH 252/315] fix: ensure proper quoting in migrations --- freqtrade/persistence/migrations.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 27970930f..3d1e8eeb7 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -35,10 +35,12 @@ def get_last_sequence_ids(engine, sequence_name: str, table_back_name: str) -> i if engine.name == "postgresql": with engine.begin() as connection: - last_id = connection.execute(text(f"select nextval('{sequence_name}')")).fetchone()[0] + last_id = connection.execute( + text(f"""select nextval('"{sequence_name}"')""") + ).fetchone()[0] with engine.begin() as connection: connection.execute( - text(f"ALTER SEQUENCE {sequence_name} rename to {table_back_name}_id_seq_bak") + text(f'ALTER SEQUENCE "{sequence_name}" rename to "{table_back_name}_id_seq_bak"') ) return last_id @@ -88,9 +90,9 @@ def drop_index_on_table(engine, inspector, table_bak_name): # 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}")) + connection.execute(text(f'drop index "{index["name"]}" on {table_bak_name}')) else: - connection.execute(text(f"drop index {index['name']}")) + connection.execute(text(f'drop index "{index["name"]}"')) def migrate_trades_and_orders_table( From 832fb7044cbdb3ad3bf1212594a06ab05b233756 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 19 Apr 2026 13:08:58 +0200 Subject: [PATCH 253/315] fix: kvstore key should allow 50 characters closes #13068 --- freqtrade/persistence/key_value_store.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/persistence/key_value_store.py b/freqtrade/persistence/key_value_store.py index ac3cedcd1..9010ad0e2 100644 --- a/freqtrade/persistence/key_value_store.py +++ b/freqtrade/persistence/key_value_store.py @@ -18,6 +18,7 @@ class ValueTypesEnum(StrEnum): INT = "int" +# must be < 50 characters to fit the database column KeyStoreKeys = Literal[ "bot_start_time", "startup_time", @@ -37,7 +38,7 @@ class _KeyValueStoreModel(ModelBase): id: Mapped[int] = mapped_column(primary_key=True) - key: Mapped[KeyStoreKeys] = mapped_column(String(25), nullable=False, index=True) + key: Mapped[KeyStoreKeys] = mapped_column(String(50), nullable=False, index=True) value_type: Mapped[ValueTypesEnum] = mapped_column(String(20), nullable=False) From 84535a14044531b1d29c83b8055e67d0593f2527 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 19 Apr 2026 13:09:40 +0200 Subject: [PATCH 254/315] chore: add migration path for kvstore --- freqtrade/persistence/migrations.py | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 3d1e8eeb7..83dfbea77 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -317,6 +317,31 @@ def migrate_pairlocks_table(decl_base, inspector, engine, pairlock_back_name: st set_sequence_ids(engine, pairlock_id=pairlock_id) +def migrate_kv_store_table(decl_base, inspector, engine, kv_store_back_name: str, cols: list): + # Schema migration necessary + with engine.begin() as connection: + connection.execute(text(f'alter table "KeyValueStore" rename to "{kv_store_back_name}"')) + + drop_index_on_table(engine, inspector, kv_store_back_name) + kv_store_id = get_last_sequence_ids(engine, "KeyValueStore_id_seq", kv_store_back_name) + + # 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 "KeyValueStore" + (id, key, value_type, string_value, datetime_value, float_value, int_value) + select id, key, value_type, string_value, datetime_value, float_value, int_value + from "{kv_store_back_name}" + """ + ) + ) + + set_sequence_ids(engine, kv_id=kv_store_id) + + def set_sqlite_to_wal(engine): if engine.name == "sqlite" and str(engine.url) != "sqlite://": # Set Mode to @@ -387,12 +412,15 @@ def check_migrate(engine: Engine, decl_base, previous_tables: list[str]) -> None cols_trades = inspector.get_columns("trades") cols_orders = inspector.get_columns("orders") cols_pairlocks = inspector.get_columns("pairlocks") + cols_kv_store = inspector.get_columns("KeyValueStore") 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") + kv_store_tabs = get_table_names_for_table(inspector, "KeyValueStore") + kv_store_back_name = get_backup_name(kv_store_tabs, "KeyValueStore_bak") # Check if migration necessary # Migrates both trades and orders table! @@ -423,6 +451,16 @@ def check_migrate(engine: Engine, decl_base, previous_tables: list[str]) -> None migrate_pairlocks_table( decl_base, inspector, engine, pairlock_table_bak_name, cols_pairlocks ) + if "KeyValueStore" in previous_tables: + key_column = next(filter(lambda x: x["name"] == "key", cols_kv_store), None) + # length of key column < 50, recreate table with correct length and migrate data + if key_column and getattr(key_column["type"], "length", -1) < 50: + migrating = True + logger.info( + f"Running database migration for KeyValueStore - backup: {kv_store_back_name}" + ) + migrate_kv_store_table(decl_base, inspector, engine, kv_store_back_name, cols_kv_store) + if "orders" not in previous_tables and "trades" in previous_tables: raise OperationalException( "Your database seems to be very old. " From b467eff8bec55f6ad6a278c3af2890a16970b067 Mon Sep 17 00:00:00 2001 From: ABS <53243996+ABSllk@users.noreply.github.com> Date: Mon, 20 Apr 2026 00:11:23 +0800 Subject: [PATCH 255/315] fix(bitget): add legacy fallback for futures stoploss cancel --- freqtrade/exchange/bitget.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/bitget.py b/freqtrade/exchange/bitget.py index 3be721cff..1119a6455 100644 --- a/freqtrade/exchange/bitget.py +++ b/freqtrade/exchange/bitget.py @@ -7,6 +7,7 @@ from freqtrade.constants import BuySell from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, PriceType, TradingMode from freqtrade.exceptions import ( DDosProtection, + InvalidOrderException, OperationalException, RetryableOrderError, TemporaryError, @@ -149,7 +150,11 @@ class Bitget(Exchange): if self.trading_mode != TradingMode.FUTURES: return self.cancel_order(order_id, pair, cancel_params) - return self.cancel_order(order_id, pair, {**cancel_params, "planType": "pos_loss"}) + try: + return self.cancel_order(order_id, pair, {**cancel_params, "planType": "pos_loss"}) + except (InvalidOrderException, IndexError): + # Keep compatibility with stoploss orders created by older versions. + return self.cancel_order(order_id, pair, cancel_params) @retrier def additional_exchange_init(self) -> None: From cd70e74d7b1d46309115a6e227d0fb81e9f92e4c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 03:03:20 +0000 Subject: [PATCH 256/315] chore(deps): bump actions/upload-artifact in the actions group Bumps the actions group with 1 update: [actions/upload-artifact](https://github.com/actions/upload-artifact). Updates `actions/upload-artifact` from 7.0.0 to 7.0.1 - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/bbbca2ddaa5d8feaa63e36b76fdaad77386f024f...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: 7.0.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 529f3d2cb..ce14e4861 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -321,7 +321,7 @@ jobs: python -m build --sdist --wheel - name: Upload artifacts 📦 - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: freqtrade-build path: | @@ -333,7 +333,7 @@ jobs: python -m build --sdist --wheel ft_client - name: Upload artifacts 📦 - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: freqtrade-client-build path: | From 9d019f1018029723090a3e2e70735b7140783eb3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 03:03:22 +0000 Subject: [PATCH 257/315] chore(deps-dev): bump nbconvert from 7.17.0 to 7.17.1 Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 7.17.0 to 7.17.1. - [Release notes](https://github.com/jupyter/nbconvert/releases) - [Changelog](https://github.com/jupyter/nbconvert/blob/main/CHANGELOG.md) - [Commits](https://github.com/jupyter/nbconvert/compare/v7.17.0...v7.17.1) --- updated-dependencies: - dependency-name: nbconvert dependency-version: 7.17.1 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 d8f8c9719..44562e000 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -20,7 +20,7 @@ pytest-xdist==3.8.0 time-machine==3.2.0 # Convert jupyter notebooks to markdown documents -nbconvert==7.17.0 +nbconvert==7.17.1 # mypy types scipy-stubs==1.17.1.3 # keep in sync with `scipy` in `requirements-hyperopt.txt` From 7d455c7e480add5bc997a362deb74f9cc4fdf79f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 03:03:54 +0000 Subject: [PATCH 258/315] chore(deps-dev): bump mypy from 1.20.0 to 1.20.1 Bumps [mypy](https://github.com/python/mypy) from 1.20.0 to 1.20.1. - [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md) - [Commits](https://github.com/python/mypy/compare/v1.20.0...v1.20.1) --- updated-dependencies: - dependency-name: mypy dependency-version: 1.20.1 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 d8f8c9719..090bcad3f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ -r docs/requirements-docs.txt ruff==0.15.9 -mypy==1.20.0 +mypy==1.20.1 pre-commit==4.5.1 pytest==9.0.3 pytest-asyncio==1.3.0 From cd58bba697b75b6d0e8c05d95f77a77e5809cb95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 03:03:56 +0000 Subject: [PATCH 259/315] chore(deps): bump peter-evans/create-pull-request from 8.1.0 to 8.1.1 Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 8.1.0 to 8.1.1. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/c0f553fe549906ede9cf27b5156039d195d2ece0...5f6978faf089d4d20b00c7766989d076bb2fc7f1) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-version: 8.1.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/binance-lev-tier-update.yml | 2 +- .github/workflows/pre-commit-update.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/binance-lev-tier-update.yml b/.github/workflows/binance-lev-tier-update.yml index cc0c11c68..628fca8f5 100644 --- a/.github/workflows/binance-lev-tier-update.yml +++ b/.github/workflows/binance-lev-tier-update.yml @@ -42,7 +42,7 @@ jobs: run: python build_helpers/binance_update_lev_tiers.py - - uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 + - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 with: token: ${{ secrets.REPO_SCOPED_TOKEN }} add-paths: freqtrade/exchange/binance_leverage_tiers.json diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml index 61da3b15e..6a918bc61 100644 --- a/.github/workflows/pre-commit-update.yml +++ b/.github/workflows/pre-commit-update.yml @@ -37,7 +37,7 @@ jobs: - name: Run auto-update run: pre-commit autoupdate - - uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 + - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 with: token: ${{ secrets.REPO_SCOPED_TOKEN }} add-paths: .pre-commit-config.yaml From 097ffd87b36d472bc8d59f562e0d2ca92c29216e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 03:04:00 +0000 Subject: [PATCH 260/315] chore(deps): bump zizmorcore/zizmor-action from 0.5.2 to 0.5.3 Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.5.2 to 0.5.3. - [Release notes](https://github.com/zizmorcore/zizmor-action/releases) - [Commits](https://github.com/zizmorcore/zizmor-action/compare/71321a20a9ded102f6e9ce5718a2fcec2c4f70d8...b1d7e1fb5de872772f31590499237e7cce841e8e) --- updated-dependencies: - dependency-name: zizmorcore/zizmor-action dependency-version: 0.5.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/zizmor_action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/zizmor_action.yml b/.github/workflows/zizmor_action.yml index aa6738715..a36439856 100644 --- a/.github/workflows/zizmor_action.yml +++ b/.github/workflows/zizmor_action.yml @@ -31,4 +31,4 @@ jobs: persist-credentials: false - name: Run zizmor 🌈 - uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2 + uses: zizmorcore/zizmor-action@b1d7e1fb5de872772f31590499237e7cce841e8e # v0.5.3 From b97f037472581280045ea1a07eadc1e86ab02e54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 03:04:01 +0000 Subject: [PATCH 261/315] chore(deps): bump plotly from 6.6.0 to 6.7.0 Bumps [plotly](https://github.com/plotly/plotly.py) from 6.6.0 to 6.7.0. - [Release notes](https://github.com/plotly/plotly.py/releases) - [Changelog](https://github.com/plotly/plotly.py/blob/main/CHANGELOG.md) - [Commits](https://github.com/plotly/plotly.py/compare/v6.6.0...v6.7.0) --- updated-dependencies: - dependency-name: plotly dependency-version: 6.7.0 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 8a66f60f7..4ab988501 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,4 +1,4 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==6.6.0 +plotly==6.7.0 From 053134297c080606ebb33e4696bd28ed7ec0227e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 03:04:06 +0000 Subject: [PATCH 262/315] chore(deps-dev): bump build from 1.4.2 to 1.4.3 Bumps [build](https://github.com/pypa/build) from 1.4.2 to 1.4.3. - [Release notes](https://github.com/pypa/build/releases) - [Changelog](https://github.com/pypa/build/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pypa/build/compare/1.4.2...1.4.3) --- updated-dependencies: - dependency-name: build dependency-version: 1.4.3 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 d8f8c9719..a2e0a2a4f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -31,6 +31,6 @@ types-tabulate==0.10.0.20260308 types-python-dateutil==2.9.0.20260402 pip-audit==2.10.0 # For build step in CI -build==1.4.2 +build==1.4.3 # For pre-commit-update check pyyaml==6.0.3 From 11e35c467f9911d84316b7142d7c088e61abf22a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 03:04:11 +0000 Subject: [PATCH 263/315] chore(deps): bump rich from 14.3.3 to 15.0.0 Bumps [rich](https://github.com/Textualize/rich) from 14.3.3 to 15.0.0. - [Release notes](https://github.com/Textualize/rich/releases) - [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md) - [Commits](https://github.com/Textualize/rich/compare/v14.3.3...v15.0.0) --- updated-dependencies: - dependency-name: rich dependency-version: 15.0.0 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 80c924deb..261a58771 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,7 +24,7 @@ tabulate==0.10.0 pycoingecko==3.2.0 jinja2==3.1.6 joblib==1.5.3 -rich==14.3.3 +rich==15.0.0 pyarrow==23.0.1; platform_machine != 'armv7l' From 5ddd4d4583732caefa937c258d578b63a5840e7b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 03:04:18 +0000 Subject: [PATCH 264/315] chore(deps): bump uvicorn from 0.43.0 to 0.44.0 Bumps [uvicorn](https://github.com/Kludex/uvicorn) from 0.43.0 to 0.44.0. - [Release notes](https://github.com/Kludex/uvicorn/releases) - [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md) - [Commits](https://github.com/Kludex/uvicorn/compare/0.43.0...0.44.0) --- updated-dependencies: - dependency-name: uvicorn dependency-version: 0.44.0 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 80c924deb..4350b931e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -39,7 +39,7 @@ sdnotify==0.3.2 # API Server fastapi==0.135.3 pydantic==2.12.5 -uvicorn==0.43.0 +uvicorn==0.44.0 pyjwt==2.12.1 aiofiles==25.1.0 psutil==7.2.2 From 4ddec7463a031bb9d242f24489162c051d58ae3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 03:04:33 +0000 Subject: [PATCH 265/315] chore(deps): bump ccxt from 4.5.48 to 4.5.49 Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.5.48 to 4.5.49. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Commits](https://github.com/ccxt/ccxt/compare/v4.5.48...v4.5.49) --- updated-dependencies: - dependency-name: ccxt dependency-version: 4.5.49 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 80c924deb..d169ee5b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ ft-pandas-ta==0.3.16 ta-lib==0.6.8 technical==1.6.0 -ccxt==4.5.48 +ccxt==4.5.49 cryptography==46.0.7 aiohttp==3.13.5 SQLAlchemy==2.0.49 From f3d227270e2ecad975a76b9732ed8eb4a6125217 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 04:31:41 +0000 Subject: [PATCH 266/315] chore(deps-dev): bump ruff from 0.15.9 to 0.15.10 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.9 to 0.15.10. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.9...0.15.10) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.10 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 d53aa8948..c7a019a2f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ -r requirements-freqai-rl.txt -r docs/requirements-docs.txt -ruff==0.15.9 +ruff==0.15.10 mypy==1.20.1 pre-commit==4.5.1 pytest==9.0.3 From 0013b3639d14973f3272d4f153cdb6e0b6bb42e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 04:32:07 +0000 Subject: [PATCH 267/315] chore(deps-dev): bump the types group with 4 updates Bumps the types group with 4 updates: [types-cachetools](https://github.com/python/typeshed), [types-requests](https://github.com/python/typeshed), [types-tabulate](https://github.com/python/typeshed) and [types-python-dateutil](https://github.com/python/typeshed). Updates `types-cachetools` from 6.2.0.20260317 to 6.2.0.20260408 - [Commits](https://github.com/python/typeshed/commits) Updates `types-requests` from 2.33.0.20260402 to 2.33.0.20260408 - [Commits](https://github.com/python/typeshed/commits) Updates `types-tabulate` from 0.10.0.20260308 to 0.10.0.20260408 - [Commits](https://github.com/python/typeshed/commits) Updates `types-python-dateutil` from 2.9.0.20260402 to 2.9.0.20260408 - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-cachetools dependency-version: 6.2.0.20260408 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types - dependency-name: types-requests dependency-version: 2.33.0.20260408 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types - dependency-name: types-tabulate dependency-version: 0.10.0.20260408 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types - dependency-name: types-python-dateutil dependency-version: 2.9.0.20260408 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d53aa8948..64bdc05bb 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -24,11 +24,11 @@ nbconvert==7.17.1 # mypy types scipy-stubs==1.17.1.3 # keep in sync with `scipy` in `requirements-hyperopt.txt` -types-cachetools==6.2.0.20260317 +types-cachetools==6.2.0.20260408 types-filelock==3.2.7 -types-requests==2.33.0.20260402 -types-tabulate==0.10.0.20260308 -types-python-dateutil==2.9.0.20260402 +types-requests==2.33.0.20260408 +types-tabulate==0.10.0.20260408 +types-python-dateutil==2.9.0.20260408 pip-audit==2.10.0 # For build step in CI build==1.4.3 From 0850f048e7f74f52e415c91515a3bf890cf12479 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 Apr 2026 06:38:13 +0200 Subject: [PATCH 268/315] chore: align pre-commit-config type versions --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index aaf398201..7dd5327cd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,11 +20,11 @@ repos: - id: mypy exclude: build_helpers additional_dependencies: - - types-cachetools==6.2.0.20260317 + - types-cachetools==6.2.0.20260408 - types-filelock==3.2.7 - - types-requests==2.33.0.20260402 - - types-tabulate==0.10.0.20260308 - - types-python-dateutil==2.9.0.20260402 + - types-requests==2.33.0.20260408 + - types-tabulate==0.10.0.20260408 + - types-python-dateutil==2.9.0.20260408 - scipy-stubs==1.17.1.3 - SQLAlchemy==2.0.49 # stages: [push] From af2e7414652bdba13fab24ede06d933e5dee33b9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 20 Apr 2026 20:03:58 +0200 Subject: [PATCH 269/315] test: add test for bitget stoploss canceling --- tests/exchange/test_bitget.py | 37 ++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/exchange/test_bitget.py b/tests/exchange/test_bitget.py index b4dafb0ae..575979aca 100644 --- a/tests/exchange/test_bitget.py +++ b/tests/exchange/test_bitget.py @@ -5,7 +5,7 @@ from unittest.mock import MagicMock, PropertyMock import pytest from freqtrade.enums import CandleType, MarginMode, RunMode, TradingMode -from freqtrade.exceptions import OperationalException, RetryableOrderError +from freqtrade.exceptions import InvalidOrderException, OperationalException, RetryableOrderError from freqtrade.exchange.common import API_RETRY_COUNT from freqtrade.util import dt_now, dt_ts, dt_utc from tests.conftest import EXMS, get_patched_exchange @@ -77,6 +77,41 @@ def test_fetch_stoploss_order_bitget_exceptions(default_conf_usdt, mocker): ) +@pytest.mark.usefixtures("init_persistence") +def test_cancel_stoploss_order_bitget(default_conf_usdt, mocker): + default_conf_usdt["dry_run"] = False + api_mock = MagicMock() + + exchange = get_patched_exchange(mocker, default_conf_usdt, api_mock, exchange="bitget") + + # Spot scenario + exchange.cancel_order = MagicMock(return_value={"id": "1234"}) + assert exchange.cancel_stoploss_order("1234", "ETH/USDT", {}) == {"id": "1234"} + assert exchange.cancel_order.call_count == 1 + exchange.cancel_order.assert_called_once_with("1234", "ETH/USDT", {"stop": True}) + + # Futures scenario + default_conf_usdt["trading_mode"] = TradingMode.FUTURES + default_conf_usdt["margin_mode"] = MarginMode.ISOLATED + exchange = get_patched_exchange(mocker, default_conf_usdt, api_mock, exchange="bitget") + exchange.cancel_order = MagicMock(return_value={"id": "1234"}) + assert exchange.cancel_stoploss_order("1234", "ETH/USDT:USDT", {}) == {"id": "1234"} + assert exchange.cancel_order.call_count == 1 + exchange.cancel_order.assert_called_once_with( + "1234", "ETH/USDT:USDT", {"stop": True, "planType": "pos_loss"} + ) + + exchange.cancel_order = MagicMock( + side_effect=[InvalidOrderException("API error"), {"id": "1234"}] + ) + assert exchange.cancel_stoploss_order("1234", "ETH/USDT:USDT", {}) == {"id": "1234"} + assert exchange.cancel_order.call_count == 2 + exchange.cancel_order.assert_any_call( + "1234", "ETH/USDT:USDT", {"stop": True, "planType": "pos_loss"} + ) + exchange.cancel_order.assert_any_call("1234", "ETH/USDT:USDT", {"stop": True}) + + def test_bitget_ohlcv_candle_limit(mocker, default_conf_usdt): # This test is also a live test - so we're sure our limits are correct. api_mock = MagicMock() From ab6f3b82222e8ba7a3f448d07abc4f0e10c1342d Mon Sep 17 00:00:00 2001 From: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com> Date: Tue, 21 Apr 2026 04:02:06 +0000 Subject: [PATCH 270/315] chore: update pre-commit hooks --- .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 7dd5327cd..08728523b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.15.10' + rev: 'v0.15.11' hooks: - id: ruff - id: ruff-format From f78e9c9014d2fbb26a1f56e96b5655663ccc9c2d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Apr 2026 06:34:28 +0200 Subject: [PATCH 271/315] chore: bump ccxt to 4.5.50 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c41c0c22b..f3143e35a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ ft-pandas-ta==0.3.16 ta-lib==0.6.8 technical==1.6.0 -ccxt==4.5.49 +ccxt==4.5.50 cryptography==46.0.7 aiohttp==3.13.5 SQLAlchemy==2.0.49 From eabaef0da2474ab32ca04a6779a3d4d18f343885 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Apr 2026 06:39:00 +0200 Subject: [PATCH 272/315] fix(ci): align action version comment --- .github/workflows/devcontainer-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/devcontainer-build.yml b/.github/workflows/devcontainer-build.yml index d29831375..c3a08c043 100644 --- a/.github/workflows/devcontainer-build.yml +++ b/.github/workflows/devcontainer-build.yml @@ -37,7 +37,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Pre-build dev container image - uses: devcontainers/ci@8bf61b26e9c3a98f69cb6ce2f88d24ff59b785c6 # v0.3.19 + uses: devcontainers/ci@8bf61b26e9c3a98f69cb6ce2f88d24ff59b785c6 # v0.3.1900000417 with: subFolder: .github imageName: ghcr.io/${{ github.repository }}-devcontainer From 2549808b5287edc6f13bc9f9ff04dd8be9d0c3d7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 19 Apr 2026 20:12:35 +0200 Subject: [PATCH 273/315] fix: add wallet_history_id to set_sequence_id logic --- freqtrade/persistence/migrations.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 83dfbea77..b7c199021 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -53,6 +53,7 @@ def set_sequence_ids( pairlock_id: int | None = None, kv_id: int | None = None, custom_data_id: int | None = None, + wallet_history_id: int | None = None, ): """ Set sequence ids to the given values. @@ -64,6 +65,7 @@ def set_sequence_ids( :param pairlock_id: value to set for pairlocks_id_seq (optional) :param kv_id: value to set for KeyValueStore_id_seq (optional) :param custom_data_id: value to set for trade_custom_data_id_seq (optional) + :param wallet_history_id: value to set for wallet_history_id_seq (optional) """ if engine.name == "postgresql": with engine.begin() as connection: @@ -83,6 +85,10 @@ def set_sequence_ids( connection.execute( text(f"ALTER SEQUENCE trade_custom_data_id_seq RESTART WITH {custom_data_id}") ) + if wallet_history_id: + connection.execute( + text(f"ALTER SEQUENCE wallet_history_id_seq RESTART WITH {wallet_history_id}") + ) def drop_index_on_table(engine, inspector, table_bak_name): From 72f9e9a05196ee66a6b30ac4ad2f136bf44acd22 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 19 Apr 2026 20:13:58 +0200 Subject: [PATCH 274/315] test: update test for wallet-history_id column --- tests/persistence/test_migrations.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tests/persistence/test_migrations.py b/tests/persistence/test_migrations.py index 2118193fa..9c2212486 100644 --- a/tests/persistence/test_migrations.py +++ b/tests/persistence/test_migrations.py @@ -383,11 +383,19 @@ def test_migrate_set_sequence_ids(): ) engine.name = "postgresql" - set_sequence_ids(engine, 22, 55, 5, 3, 1) + set_sequence_ids( + engine, + order_id=22, + trade_id=55, + pairlock_id=5, + kv_id=3, + custom_data_id=10, + wallet_history_id=15, + ) # begin called once and connection.execute invoked for each provided sequence id assert engine.begin.call_count == 1 - assert conn.execute.call_count == 5 + assert conn.execute.call_count == 6 assert ( conn.execute.call_args_list[0][0][0].text == "ALTER SEQUENCE orders_id_seq RESTART WITH 22" ) @@ -404,7 +412,12 @@ def test_migrate_set_sequence_ids(): ) assert ( conn.execute.call_args_list[4][0][0].text - == "ALTER SEQUENCE trade_custom_data_id_seq RESTART WITH 1" + == "ALTER SEQUENCE trade_custom_data_id_seq RESTART WITH 10" + ) + + assert ( + conn.execute.call_args_list[5][0][0].text + == "ALTER SEQUENCE wallet_history_id_seq RESTART WITH 15" ) engine.reset_mock() From 85af9dd3fc3f9b39d5d8ebecd28a4c6a5a6f6a27 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Apr 2026 07:01:39 +0200 Subject: [PATCH 275/315] fix: migrate wallet_history table --- freqtrade/commands/db_commands.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/freqtrade/commands/db_commands.py b/freqtrade/commands/db_commands.py index d5acf8f14..3e20741ea 100644 --- a/freqtrade/commands/db_commands.py +++ b/freqtrade/commands/db_commands.py @@ -17,6 +17,7 @@ def start_convert_db(args: dict[str, Any]) -> None: from freqtrade.persistence.key_value_store import _KeyValueStoreModel from freqtrade.persistence.migrations import set_sequence_ids from freqtrade.persistence.pairlock import PairLock + from freqtrade.persistence.wallet_history import WalletHistory config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) @@ -29,6 +30,7 @@ def start_convert_db(args: dict[str, Any]) -> None: pairlock_count = 0 kv_count = 0 custom_data_count = 0 + wallet_history_count = 0 for trade in Trade.get_trades(): trade_count += 1 make_transient(trade) @@ -57,12 +59,19 @@ def start_convert_db(args: dict[str, Any]) -> None: session_target.add(cd) session_target.commit() + for wh in WalletHistory.session.scalars(select(WalletHistory)): + wallet_history_count += 1 + make_transient(wh) + session_target.add(wh) + session_target.commit() + # Update sequences max_trade_id = session_target.scalar(select(func.max(Trade.id))) max_order_id = session_target.scalar(select(func.max(Order.id))) max_pairlock_id = session_target.scalar(select(func.max(PairLock.id))) max_kv_id = session_target.scalar(select(func.max(_KeyValueStoreModel.id))) max_custom_data_id = session_target.scalar(select(func.max(_CustomData.id))) + max_wallet_history_id = session_target.scalar(select(func.max(WalletHistory.id))) set_sequence_ids( session_target.get_bind(), @@ -71,9 +80,11 @@ def start_convert_db(args: dict[str, Any]) -> None: pairlock_id=(max_pairlock_id or 0) + 1, kv_id=(max_kv_id or 0) + 1, custom_data_id=(max_custom_data_id or 0) + 1, + wallet_history_id=(max_wallet_history_id or 0) + 1, ) logger.info( f"Migrated {trade_count} Trades, {pairlock_count} Pairlocks, " - f"{kv_count} Key-Value pairs, and {custom_data_count} Custom Data entries." + f"{kv_count} Key-Value pairs, {custom_data_count} Custom Data entries, " + f"and {wallet_history_count} Wallet History entries." ) From 9dfbe8cf63343ec5dfbb724cd59cb2d40ae90a51 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Apr 2026 07:07:05 +0200 Subject: [PATCH 276/315] refactor: move db-migration to persistence --- freqtrade/commands/db_commands.py | 74 +----------------------- freqtrade/persistence/db_migration.py | 81 +++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 71 deletions(-) create mode 100644 freqtrade/persistence/db_migration.py diff --git a/freqtrade/commands/db_commands.py b/freqtrade/commands/db_commands.py index 3e20741ea..384af6e2f 100644 --- a/freqtrade/commands/db_commands.py +++ b/freqtrade/commands/db_commands.py @@ -8,16 +8,10 @@ logger = logging.getLogger(__name__) def start_convert_db(args: dict[str, Any]) -> None: - from sqlalchemy import func, select - from sqlalchemy.orm import make_transient from freqtrade.configuration.config_setup import setup_utils_configuration - from freqtrade.persistence import Order, Trade, init_db - from freqtrade.persistence.custom_data import _CustomData - from freqtrade.persistence.key_value_store import _KeyValueStoreModel - from freqtrade.persistence.migrations import set_sequence_ids - from freqtrade.persistence.pairlock import PairLock - from freqtrade.persistence.wallet_history import WalletHistory + from freqtrade.persistence import Trade, init_db + from freqtrade.persistence.db_migration import migrate_db config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) @@ -25,66 +19,4 @@ def start_convert_db(args: dict[str, Any]) -> None: session_target = Trade.session init_db(config["db_url_from"]) logger.info("Starting db migration.") - - trade_count = 0 - pairlock_count = 0 - kv_count = 0 - custom_data_count = 0 - wallet_history_count = 0 - for trade in Trade.get_trades(): - trade_count += 1 - make_transient(trade) - for o in trade.orders: - make_transient(o) - - session_target.add(trade) - - session_target.commit() - - for pairlock in PairLock.get_all_locks(): - pairlock_count += 1 - make_transient(pairlock) - session_target.add(pairlock) - session_target.commit() - - for kv in _KeyValueStoreModel.session.scalars(select(_KeyValueStoreModel)): - kv_count += 1 - make_transient(kv) - session_target.add(kv) - session_target.commit() - - for cd in _CustomData.session.scalars(select(_CustomData)): - custom_data_count += 1 - make_transient(cd) - session_target.add(cd) - session_target.commit() - - for wh in WalletHistory.session.scalars(select(WalletHistory)): - wallet_history_count += 1 - make_transient(wh) - session_target.add(wh) - session_target.commit() - - # Update sequences - max_trade_id = session_target.scalar(select(func.max(Trade.id))) - max_order_id = session_target.scalar(select(func.max(Order.id))) - max_pairlock_id = session_target.scalar(select(func.max(PairLock.id))) - max_kv_id = session_target.scalar(select(func.max(_KeyValueStoreModel.id))) - max_custom_data_id = session_target.scalar(select(func.max(_CustomData.id))) - max_wallet_history_id = session_target.scalar(select(func.max(WalletHistory.id))) - - set_sequence_ids( - session_target.get_bind(), - trade_id=(max_trade_id or 0) + 1, - order_id=(max_order_id or 0) + 1, - pairlock_id=(max_pairlock_id or 0) + 1, - kv_id=(max_kv_id or 0) + 1, - custom_data_id=(max_custom_data_id or 0) + 1, - wallet_history_id=(max_wallet_history_id or 0) + 1, - ) - - logger.info( - f"Migrated {trade_count} Trades, {pairlock_count} Pairlocks, " - f"{kv_count} Key-Value pairs, {custom_data_count} Custom Data entries, " - f"and {wallet_history_count} Wallet History entries." - ) + migrate_db(session_target) diff --git a/freqtrade/persistence/db_migration.py b/freqtrade/persistence/db_migration.py new file mode 100644 index 000000000..9c535894c --- /dev/null +++ b/freqtrade/persistence/db_migration.py @@ -0,0 +1,81 @@ +import logging + +from sqlalchemy import func, select +from sqlalchemy.orm import make_transient + +from freqtrade.persistence.base import SessionType +from freqtrade.persistence.custom_data import _CustomData +from freqtrade.persistence.key_value_store import _KeyValueStoreModel +from freqtrade.persistence.migrations import set_sequence_ids +from freqtrade.persistence.pairlock import PairLock +from freqtrade.persistence.trade_model import Order, Trade +from freqtrade.persistence.wallet_history import WalletHistory + + +logger = logging.getLogger(__name__) + + +def migrate_db(session_target: SessionType): + + trade_count = 0 + pairlock_count = 0 + kv_count = 0 + custom_data_count = 0 + wallet_history_count = 0 + for trade in Trade.get_trades(): + trade_count += 1 + make_transient(trade) + for o in trade.orders: + make_transient(o) + + session_target.add(trade) + + session_target.commit() + + for pairlock in PairLock.get_all_locks(): + pairlock_count += 1 + make_transient(pairlock) + session_target.add(pairlock) + session_target.commit() + + for kv in _KeyValueStoreModel.session.scalars(select(_KeyValueStoreModel)): + kv_count += 1 + make_transient(kv) + session_target.add(kv) + session_target.commit() + + for cd in _CustomData.session.scalars(select(_CustomData)): + custom_data_count += 1 + make_transient(cd) + session_target.add(cd) + session_target.commit() + + for wh in WalletHistory.session.scalars(select(WalletHistory)): + wallet_history_count += 1 + make_transient(wh) + session_target.add(wh) + session_target.commit() + + # Update sequences + max_trade_id = session_target.scalar(select(func.max(Trade.id))) + max_order_id = session_target.scalar(select(func.max(Order.id))) + max_pairlock_id = session_target.scalar(select(func.max(PairLock.id))) + max_kv_id = session_target.scalar(select(func.max(_KeyValueStoreModel.id))) + max_custom_data_id = session_target.scalar(select(func.max(_CustomData.id))) + max_wallet_history_id = session_target.scalar(select(func.max(WalletHistory.id))) + + set_sequence_ids( + session_target.get_bind(), + trade_id=(max_trade_id or 0) + 1, + order_id=(max_order_id or 0) + 1, + pairlock_id=(max_pairlock_id or 0) + 1, + kv_id=(max_kv_id or 0) + 1, + custom_data_id=(max_custom_data_id or 0) + 1, + wallet_history_id=(max_wallet_history_id or 0) + 1, + ) + + logger.info( + f"Migrated {trade_count} Trades, {pairlock_count} Pairlocks, " + f"{kv_count} Key-Value pairs, {custom_data_count} Custom Data entries, " + f"and {wallet_history_count} Wallet History entries." + ) From 3e3436e432e95364aab76bd2aee421ed01936757 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Apr 2026 07:17:32 +0200 Subject: [PATCH 277/315] test: add explicit test for db_migration --- tests/persistence/test_db_migration.py | 69 ++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 tests/persistence/test_db_migration.py diff --git a/tests/persistence/test_db_migration.py b/tests/persistence/test_db_migration.py new file mode 100644 index 000000000..5cd75f038 --- /dev/null +++ b/tests/persistence/test_db_migration.py @@ -0,0 +1,69 @@ +from unittest.mock import MagicMock + +from freqtrade.persistence.base import ModelBase +from freqtrade.persistence.custom_data import _CustomData +from freqtrade.persistence.db_migration import migrate_db +from freqtrade.persistence.key_value_store import _KeyValueStoreModel +from freqtrade.persistence.pairlock import PairLock +from freqtrade.persistence.trade_model import Trade +from freqtrade.persistence.wallet_history import WalletHistory + + +def test_migrate_db_detail(mocker): + # Expected models to be migrated based on the registered models + expected_models = {mapper.class_.__name__ for mapper in ModelBase.registry.mappers} + session_target = MagicMock() + + order = MagicMock() + trade = MagicMock(orders=[order]) + pairlock = MagicMock() + kv = MagicMock() + custom_data = MagicMock() + wallet_history = MagicMock() + + kv_session = MagicMock() + kv_session.scalars.return_value = [kv] + custom_data_session = MagicMock() + custom_data_session.scalars.return_value = [custom_data] + wallet_history_session = MagicMock() + wallet_history_session.scalars.return_value = [wallet_history] + + mocker.patch.object(Trade, "get_trades", return_value=[trade]) + mocker.patch.object(PairLock, "get_all_locks", return_value=[pairlock]) + mocker.patch.object(_KeyValueStoreModel, "session", kv_session, create=True) + mocker.patch.object(_CustomData, "session", custom_data_session, create=True) + mocker.patch.object(WalletHistory, "session", wallet_history_session, create=True) + + make_transient_mock = mocker.patch("freqtrade.persistence.db_migration.make_transient") + set_sequence_ids_mock = mocker.patch("freqtrade.persistence.db_migration.set_sequence_ids") + + # max ids for Trade, Order, PairLock, KeyValueStore, CustomData, WalletHistory + session_target.scalar.side_effect = [10, 11, 12, 13, 14, 15] + session_target.get_bind.return_value = "bind" + + migrate_db(session_target) + + assert session_target.add.call_count == 5 + # Order objects are linked to trades, so they are not added explicitly + + assert session_target.add.call_count == len(expected_models) - 1 + session_target.add.assert_any_call(trade) + session_target.add.assert_any_call(pairlock) + session_target.add.assert_any_call(kv) + session_target.add.assert_any_call(custom_data) + session_target.add.assert_any_call(wallet_history) + + assert session_target.commit.call_count == 5 + assert make_transient_mock.call_count == 6 + make_transient_mock.assert_any_call(trade) + make_transient_mock.assert_any_call(order) + + set_sequence_ids_mock.assert_called_once_with( + "bind", + trade_id=11, + order_id=12, + pairlock_id=13, + kv_id=14, + custom_data_id=15, + wallet_history_id=16, + ) From f2f17f6d7b002e3a409309847e232f7d4146774e Mon Sep 17 00:00:00 2001 From: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com> Date: Thu, 23 Apr 2026 03:36:46 +0000 Subject: [PATCH 278/315] chore: update binance leverage tiers --- .../exchange/binance_leverage_tiers.json | 2134 ++++++++++------- 1 file changed, 1240 insertions(+), 894 deletions(-) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index 09b8e5791..f9fa032b5 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -12515,6 +12515,110 @@ } } ], + "AVGO/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "AVGO/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 1, + "initialLeverage": 10, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.05, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "AVGO/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 2, + "initialLeverage": 5, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.1, + "cum": 250.0 + } + }, + { + "tier": 3.0, + "symbol": "AVGO/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 3, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 10000, + "maintMarginRatio": 0.125, + "cum": 500.0 + } + }, + { + "tier": 4.0, + "symbol": "AVGO/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 4, + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 4670.0 + } + }, + { + "tier": 5.0, + "symbol": "AVGO/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 5, + "initialLeverage": 2, + "notionalCap": 8000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 46320.0 + } + }, + { + "tier": 6.0, + "symbol": "AVGO/USDT:USDT", + "currency": "USDT", + "minNotional": 8000000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 6, + "initialLeverage": 1, + "notionalCap": 15000000, + "notionalFloor": 8000000, + "maintMarginRatio": 0.5, + "cum": 2046320.0 + } + } + ], "AVNT/USDT:USDT": [ { "tier": 1.0, @@ -13607,14 +13711,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maintenanceMarginRate": 0.04, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 25, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.02, + "maintMarginRatio": 0.04, "cum": 0.0 } }, @@ -13624,15 +13728,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 2, - "initialLeverage": 20, + "initialLeverage": 10, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.025, - "cum": 25.0 + "maintMarginRatio": 0.05, + "cum": 50.0 } }, { @@ -13640,37 +13744,20 @@ "symbol": "B3/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": 3, - "initialLeverage": 10, - "notionalCap": 20000, - "notionalFloor": 10000, - "maintMarginRatio": 0.05, - "cum": 275.0 - } - }, - { - "tier": 4.0, - "symbol": "B3/USDT:USDT", - "currency": "USDT", - "minNotional": 20000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": 4, + "bracket": 3, "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 20000, + "notionalFloor": 10000, "maintMarginRatio": 0.1, - "cum": 1275.0 + "cum": 550.0 } }, { - "tier": 5.0, + "tier": 4.0, "symbol": "B3/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, @@ -13678,16 +13765,16 @@ "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 5, + "bracket": 4, "initialLeverage": 4, "notionalCap": 100000, "notionalFloor": 50000, "maintMarginRatio": 0.125, - "cum": 2525.0 + "cum": 1800.0 } }, { - "tier": 6.0, + "tier": 5.0, "symbol": "B3/USDT:USDT", "currency": "USDT", "minNotional": 100000.0, @@ -13695,46 +13782,150 @@ "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 6, + "bracket": 5, "initialLeverage": 3, "notionalCap": 250000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 6695.0 + "cum": 5970.0 + } + }, + { + "tier": 6.0, + "symbol": "B3/USDT:USDT", + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 6, + "initialLeverage": 2, + "notionalCap": 300000, + "notionalFloor": 250000, + "maintMarginRatio": 0.25, + "cum": 26795.0 } }, { "tier": 7.0, "symbol": "B3/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 2500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 2500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.25, - "cum": 27520.0 - } - }, - { - "tier": 8.0, - "symbol": "B3/USDT:USDT", - "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 300000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 7, "initialLeverage": 1, - "notionalCap": 5000000, - "notionalFloor": 2500000, + "notionalCap": 500000, + "notionalFloor": 300000, "maintMarginRatio": 0.5, - "cum": 652520.0 + "cum": 101795.0 + } + } + ], + "BABA/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "BABA/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 1, + "initialLeverage": 10, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.05, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "BABA/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 2, + "initialLeverage": 5, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.1, + "cum": 250.0 + } + }, + { + "tier": 3.0, + "symbol": "BABA/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 3, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 10000, + "maintMarginRatio": 0.125, + "cum": 500.0 + } + }, + { + "tier": 4.0, + "symbol": "BABA/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 4, + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 4670.0 + } + }, + { + "tier": 5.0, + "symbol": "BABA/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 5, + "initialLeverage": 2, + "notionalCap": 8000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 46320.0 + } + }, + { + "tier": 6.0, + "symbol": "BABA/USDT:USDT", + "currency": "USDT", + "minNotional": 8000000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 6, + "initialLeverage": 1, + "notionalCap": 15000000, + "notionalFloor": 8000000, + "maintMarginRatio": 0.5, + "cum": 2046320.0 } } ], @@ -18848,15 +19039,15 @@ "symbol": "BOB/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 1, - "initialLeverage": 20, - "notionalCap": 5000, + "initialLeverage": 10, + "notionalCap": 10000, "notionalFloor": 0, - "maintMarginRatio": 0.025, + "maintMarginRatio": 0.05, "cum": 0.0 } }, @@ -18864,38 +19055,21 @@ "tier": 2.0, "symbol": "BOB/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": 2, - "initialLeverage": 10, - "notionalCap": 10000, - "notionalFloor": 5000, - "maintMarginRatio": 0.05, - "cum": 125.0 - } - }, - { - "tier": 3.0, - "symbol": "BOB/USDT:USDT", - "currency": "USDT", "minNotional": 10000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": 3, + "bracket": 2, "initialLeverage": 5, "notionalCap": 50000, "notionalFloor": 10000, "maintMarginRatio": 0.1, - "cum": 625.0 + "cum": 500.0 } }, { - "tier": 4.0, + "tier": 3.0, "symbol": "BOB/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, @@ -18903,16 +19077,16 @@ "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 4, + "bracket": 3, "initialLeverage": 4, "notionalCap": 100000, "notionalFloor": 50000, "maintMarginRatio": 0.125, - "cum": 1875.0 + "cum": 1750.0 } }, { - "tier": 5.0, + "tier": 4.0, "symbol": "BOB/USDT:USDT", "currency": "USDT", "minNotional": 100000.0, @@ -18920,46 +19094,46 @@ "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 5, + "bracket": 4, "initialLeverage": 3, "notionalCap": 250000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 6045.0 + "cum": 5920.0 + } + }, + { + "tier": 5.0, + "symbol": "BOB/USDT:USDT", + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 5, + "initialLeverage": 2, + "notionalCap": 400000, + "notionalFloor": 250000, + "maintMarginRatio": 0.25, + "cum": 26745.0 } }, { "tier": 6.0, "symbol": "BOB/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, + "minNotional": 400000.0, "maxNotional": 500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 6, - "initialLeverage": 2, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.25, - "cum": 26870.0 - } - }, - { - "tier": 7.0, - "symbol": "BOB/USDT:USDT", - "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 800000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 7, + "bracket": 6, "initialLeverage": 1, - "notionalCap": 800000, - "notionalFloor": 500000, + "notionalCap": 500000, + "notionalFloor": 400000, "maintMarginRatio": 0.5, - "cum": 151870.0 + "cum": 126745.0 } } ], @@ -20927,144 +21101,6 @@ } } ], - "BTC/USDT:USDT-260327": [ - { - "tier": 1.0, - "symbol": "BTC/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 50.0, - "info": { - "bracket": 1, - "initialLeverage": 50, - "notionalCap": 50000, - "notionalFloor": 0, - "maintMarginRatio": 0.01, - "cum": 0.0 - } - }, - { - "tier": 2.0, - "symbol": "BTC/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 375000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, - "info": { - "bracket": 2, - "initialLeverage": 25, - "notionalCap": 375000, - "notionalFloor": 50000, - "maintMarginRatio": 0.02, - "cum": 500.0 - } - }, - { - "tier": 3.0, - "symbol": "BTC/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 375000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": 3, - "initialLeverage": 10, - "notionalCap": 2000000, - "notionalFloor": 375000, - "maintMarginRatio": 0.05, - "cum": 11750.0 - } - }, - { - "tier": 4.0, - "symbol": "BTC/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": 4, - "initialLeverage": 5, - "notionalCap": 4000000, - "notionalFloor": 2000000, - "maintMarginRatio": 0.1, - "cum": 111750.0 - } - }, - { - "tier": 5.0, - "symbol": "BTC/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 5, - "initialLeverage": 4, - "notionalCap": 10000000, - "notionalFloor": 4000000, - "maintMarginRatio": 0.125, - "cum": 211750.0 - } - }, - { - "tier": 6.0, - "symbol": "BTC/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, - "maintenanceMarginRate": 0.15, - "maxLeverage": 3.0, - "info": { - "bracket": 6, - "initialLeverage": 3, - "notionalCap": 20000000, - "notionalFloor": 10000000, - "maintMarginRatio": 0.15, - "cum": 461750.0 - } - }, - { - "tier": 7.0, - "symbol": "BTC/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 20000000.0, - "maxNotional": 40000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 40000000, - "notionalFloor": 20000000, - "maintMarginRatio": 0.25, - "cum": 2461750.0 - } - }, - { - "tier": 8.0, - "symbol": "BTC/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 40000000.0, - "maxNotional": 120000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": 8, - "initialLeverage": 1, - "notionalCap": 120000000, - "notionalFloor": 40000000, - "maintMarginRatio": 0.5, - "cum": 12461750.0 - } - } - ], "BTC/USDT:USDT-260626": [ { "tier": 1.0, @@ -24031,6 +24067,161 @@ } } ], + "CHIP/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "CHIP/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, + "info": { + "bracket": 1, + "initialLeverage": 50, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.015, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "CHIP/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.02, + "maxLeverage": 25.0, + "info": { + "bracket": 2, + "initialLeverage": 25, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.02, + "cum": 25.0 + } + }, + { + "tier": 3.0, + "symbol": "CHIP/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": 3, + "initialLeverage": 20, + "notionalCap": 25000, + "notionalFloor": 10000, + "maintMarginRatio": 0.025, + "cum": 75.0 + } + }, + { + "tier": 4.0, + "symbol": "CHIP/USDT:USDT", + "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 62500.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 4, + "initialLeverage": 10, + "notionalCap": 62500, + "notionalFloor": 25000, + "maintMarginRatio": 0.05, + "cum": 700.0 + } + }, + { + "tier": 5.0, + "symbol": "CHIP/USDT:USDT", + "currency": "USDT", + "minNotional": 62500.0, + "maxNotional": 125000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 5, + "initialLeverage": 5, + "notionalCap": 125000, + "notionalFloor": 62500, + "maintMarginRatio": 0.1, + "cum": 3825.0 + } + }, + { + "tier": 6.0, + "symbol": "CHIP/USDT:USDT", + "currency": "USDT", + "minNotional": 125000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 6, + "initialLeverage": 4, + "notionalCap": 250000, + "notionalFloor": 125000, + "maintMarginRatio": 0.125, + "cum": 6950.0 + } + }, + { + "tier": 7.0, + "symbol": "CHIP/USDT:USDT", + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 7, + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 17375.0 + } + }, + { + "tier": 8.0, + "symbol": "CHIP/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 8, + "initialLeverage": 2, + "notionalCap": 7500000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 59025.0 + } + }, + { + "tier": 9.0, + "symbol": "CHIP/USDT:USDT", + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 12500000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 9, + "initialLeverage": 1, + "notionalCap": 12500000, + "notionalFloor": 7500000, + "maintMarginRatio": 0.5, + "cum": 1934025.0 + } + } + ], "CHR/USDT:USDT": [ { "tier": 1.0, @@ -28090,13 +28281,13 @@ "symbol": "DAM/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 2500000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": 5, "initialLeverage": 2, - "notionalCap": 2500000, + "notionalCap": 400000, "notionalFloor": 250000, "maintMarginRatio": 0.25, "cum": 26745.0 @@ -28106,17 +28297,17 @@ "tier": 6.0, "symbol": "DAM/USDT:USDT", "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 400000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": 6, "initialLeverage": 1, - "notionalCap": 5000000, - "notionalFloor": 2500000, + "notionalCap": 500000, + "notionalFloor": 400000, "maintMarginRatio": 0.5, - "cum": 651745.0 + "cum": 126745.0 } } ], @@ -28730,14 +28921,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.04, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.04, "cum": 0.0 } }, @@ -28747,14 +28938,14 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 2, - "initialLeverage": 20, + "initialLeverage": 10, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.025, + "maintMarginRatio": 0.05, "cum": 50.0 } }, @@ -28763,50 +28954,50 @@ "symbol": "DEGEN/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 3, - "initialLeverage": 10, - "notionalCap": 20000, + "initialLeverage": 5, + "notionalCap": 50000, "notionalFloor": 10000, - "maintMarginRatio": 0.05, - "cum": 300.0 + "maintMarginRatio": 0.1, + "cum": 550.0 } }, { "tier": 4.0, "symbol": "DEGEN/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 4, - "initialLeverage": 5, - "notionalCap": 50000, - "notionalFloor": 20000, - "maintMarginRatio": 0.1, - "cum": 1300.0 + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 50000, + "maintMarginRatio": 0.125, + "cum": 1800.0 } }, { "tier": 5.0, "symbol": "DEGEN/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, + "minNotional": 100000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 5, - "initialLeverage": 4, + "initialLeverage": 3, "notionalCap": 250000, - "notionalFloor": 50000, - "maintMarginRatio": 0.125, - "cum": 2550.0 + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 5970.0 } }, { @@ -28814,50 +29005,33 @@ "symbol": "DEGEN/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 6, - "initialLeverage": 3, - "notionalCap": 500000, + "initialLeverage": 2, + "notionalCap": 400000, "notionalFloor": 250000, - "maintMarginRatio": 0.1667, - "cum": 12975.0 + "maintMarginRatio": 0.25, + "cum": 26795.0 } }, { "tier": 7.0, "symbol": "DEGEN/USDT:USDT", "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 7500000, - "notionalFloor": 500000, - "maintMarginRatio": 0.25, - "cum": 54625.0 - } - }, - { - "tier": 8.0, - "symbol": "DEGEN/USDT:USDT", - "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 12500000.0, + "minNotional": 400000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 7, "initialLeverage": 1, - "notionalCap": 12500000, - "notionalFloor": 7500000, + "notionalCap": 500000, + "notionalFloor": 400000, "maintMarginRatio": 0.5, - "cum": 1929625.0 + "cum": 126795.0 } } ], @@ -28918,13 +29092,13 @@ "symbol": "DEGO/USDT:USDT", "currency": "USDT", "minNotional": 70000.0, - "maxNotional": 700000.0, + "maxNotional": 200000.0, "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { "bracket": 4, "initialLeverage": 3, - "notionalCap": 700000, + "notionalCap": 200000, "notionalFloor": 70000, "maintMarginRatio": 0.1667, "cum": 4919.0 @@ -28934,34 +29108,34 @@ "tier": 5.0, "symbol": "DEGO/USDT:USDT", "currency": "USDT", - "minNotional": 700000.0, - "maxNotional": 2500000.0, + "minNotional": 200000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": 5, "initialLeverage": 2, - "notionalCap": 2500000, - "notionalFloor": 700000, + "notionalCap": 500000, + "notionalFloor": 200000, "maintMarginRatio": 0.25, - "cum": 63229.0 + "cum": 21579.0 } }, { "tier": 6.0, "symbol": "DEGO/USDT:USDT", "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 500000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": 6, "initialLeverage": 1, - "notionalCap": 5000000, - "notionalFloor": 2500000, + "notionalCap": 800000, + "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 688229.0 + "cum": 146579.0 } } ], @@ -28971,15 +29145,15 @@ "symbol": "DENT/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maxNotional": 15000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 1, - "initialLeverage": 50, - "notionalCap": 5000, + "initialLeverage": 10, + "notionalCap": 15000, "notionalFloor": 0, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.05, "cum": 0.0 } }, @@ -28987,136 +29161,85 @@ "tier": 2.0, "symbol": "DENT/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "minNotional": 15000.0, + "maxNotional": 80000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 2, - "initialLeverage": 25, - "notionalCap": 10000, - "notionalFloor": 5000, - "maintMarginRatio": 0.02, - "cum": 25.0 + "initialLeverage": 5, + "notionalCap": 80000, + "notionalFloor": 15000, + "maintMarginRatio": 0.1, + "cum": 750.0 } }, { "tier": 3.0, "symbol": "DENT/USDT:USDT", "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "minNotional": 80000.0, + "maxNotional": 200000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 3, - "initialLeverage": 20, - "notionalCap": 25000, - "notionalFloor": 10000, - "maintMarginRatio": 0.025, - "cum": 75.0 + "initialLeverage": 4, + "notionalCap": 200000, + "notionalFloor": 80000, + "maintMarginRatio": 0.125, + "cum": 2750.0 } }, { "tier": 4.0, "symbol": "DENT/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 62500.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 200000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 4, - "initialLeverage": 10, - "notionalCap": 62500, - "notionalFloor": 25000, - "maintMarginRatio": 0.05, - "cum": 700.0 + "initialLeverage": 3, + "notionalCap": 300000, + "notionalFloor": 200000, + "maintMarginRatio": 0.1667, + "cum": 11090.0 } }, { "tier": 5.0, "symbol": "DENT/USDT:USDT", "currency": "USDT", - "minNotional": 62500.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 300000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 5, - "initialLeverage": 5, - "notionalCap": 125000, - "notionalFloor": 62500, - "maintMarginRatio": 0.1, - "cum": 3825.0 + "initialLeverage": 2, + "notionalCap": 500000, + "notionalFloor": 300000, + "maintMarginRatio": 0.25, + "cum": 36080.0 } }, { "tier": 6.0, "symbol": "DENT/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 6, - "initialLeverage": 4, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.125, - "cum": 6950.0 - } - }, - { - "tier": 7.0, - "symbol": "DENT/USDT:USDT", - "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 7, - "initialLeverage": 3, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.1667, - "cum": 17375.0 - } - }, - { - "tier": 8.0, - "symbol": "DENT/USDT:USDT", - "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 8, - "initialLeverage": 2, - "notionalCap": 7500000, - "notionalFloor": 500000, - "maintMarginRatio": 0.25, - "cum": 59025.0 - } - }, - { - "tier": 9.0, - "symbol": "DENT/USDT:USDT", - "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 12500000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 9, + "bracket": 6, "initialLeverage": 1, - "notionalCap": 12500000, - "notionalFloor": 7500000, + "notionalCap": 800000, + "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 1934025.0 + "cum": 161080.0 } } ], @@ -34496,144 +34619,6 @@ } } ], - "ETH/USDT:USDT-260327": [ - { - "tier": 1.0, - "symbol": "ETH/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 50.0, - "info": { - "bracket": 1, - "initialLeverage": 50, - "notionalCap": 50000, - "notionalFloor": 0, - "maintMarginRatio": 0.01, - "cum": 0.0 - } - }, - { - "tier": 2.0, - "symbol": "ETH/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 375000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, - "info": { - "bracket": 2, - "initialLeverage": 25, - "notionalCap": 375000, - "notionalFloor": 50000, - "maintMarginRatio": 0.02, - "cum": 500.0 - } - }, - { - "tier": 3.0, - "symbol": "ETH/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 375000.0, - "maxNotional": 2000000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": 3, - "initialLeverage": 10, - "notionalCap": 2000000, - "notionalFloor": 375000, - "maintMarginRatio": 0.05, - "cum": 11750.0 - } - }, - { - "tier": 4.0, - "symbol": "ETH/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 2000000.0, - "maxNotional": 4000000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": 4, - "initialLeverage": 5, - "notionalCap": 4000000, - "notionalFloor": 2000000, - "maintMarginRatio": 0.1, - "cum": 111750.0 - } - }, - { - "tier": 5.0, - "symbol": "ETH/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 4000000.0, - "maxNotional": 10000000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 5, - "initialLeverage": 4, - "notionalCap": 10000000, - "notionalFloor": 4000000, - "maintMarginRatio": 0.125, - "cum": 211750.0 - } - }, - { - "tier": 6.0, - "symbol": "ETH/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 10000000.0, - "maxNotional": 20000000.0, - "maintenanceMarginRate": 0.15, - "maxLeverage": 3.0, - "info": { - "bracket": 6, - "initialLeverage": 3, - "notionalCap": 20000000, - "notionalFloor": 10000000, - "maintMarginRatio": 0.15, - "cum": 461750.0 - } - }, - { - "tier": 7.0, - "symbol": "ETH/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 20000000.0, - "maxNotional": 40000000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 40000000, - "notionalFloor": 20000000, - "maintMarginRatio": 0.25, - "cum": 2461750.0 - } - }, - { - "tier": 8.0, - "symbol": "ETH/USDT:USDT-260327", - "currency": "USDT", - "minNotional": 40000000.0, - "maxNotional": 120000000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": 8, - "initialLeverage": 1, - "notionalCap": 120000000, - "notionalFloor": 40000000, - "maintMarginRatio": 0.5, - "cum": 12461750.0 - } - } - ], "ETH/USDT:USDT-260626": [ { "tier": 1.0, @@ -35675,14 +35660,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 10, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.05, + "maintMarginRatio": 0.025, "cum": 0.0 } }, @@ -35692,15 +35677,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maintenanceMarginRate": 0.0333, + "maxLeverage": 15.0, "info": { "bracket": 2, - "initialLeverage": 5, + "initialLeverage": 15, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.1, - "cum": 250.0 + "maintMarginRatio": 0.0333, + "cum": 41.5 } }, { @@ -35708,37 +35693,71 @@ "symbol": "EWJ/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 4, - "notionalCap": 100000, + "initialLeverage": 10, + "notionalCap": 25000, "notionalFloor": 10000, - "maintMarginRatio": 0.125, - "cum": 500.0 + "maintMarginRatio": 0.05, + "cum": 208.5 } }, { "tier": 4.0, "symbol": "EWJ/USDT:USDT", "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 4, + "initialLeverage": 5, + "notionalCap": 50000, + "notionalFloor": 25000, + "maintMarginRatio": 0.1, + "cum": 1458.5 + } + }, + { + "tier": 5.0, + "symbol": "EWJ/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 5, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 50000, + "maintMarginRatio": 0.125, + "cum": 2708.5 + } + }, + { + "tier": 6.0, + "symbol": "EWJ/USDT:USDT", + "currency": "USDT", "minNotional": 100000.0, "maxNotional": 500000.0, "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 4, + "bracket": 6, "initialLeverage": 3, "notionalCap": 500000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 4670.0 + "cum": 6878.5 } }, { - "tier": 5.0, + "tier": 7.0, "symbol": "EWJ/USDT:USDT", "currency": "USDT", "minNotional": 500000.0, @@ -35746,16 +35765,16 @@ "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 5, + "bracket": 7, "initialLeverage": 2, "notionalCap": 8000000, "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 46320.0 + "cum": 48528.5 } }, { - "tier": 6.0, + "tier": 8.0, "symbol": "EWJ/USDT:USDT", "currency": "USDT", "minNotional": 8000000.0, @@ -35763,12 +35782,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 6, + "bracket": 8, "initialLeverage": 1, "notionalCap": 15000000, "notionalFloor": 8000000, "maintMarginRatio": 0.5, - "cum": 2046320.0 + "cum": 2048528.5 } } ], @@ -35779,14 +35798,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 10, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.05, + "maintMarginRatio": 0.025, "cum": 0.0 } }, @@ -35796,15 +35815,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maintenanceMarginRate": 0.0333, + "maxLeverage": 15.0, "info": { "bracket": 2, - "initialLeverage": 5, + "initialLeverage": 15, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.1, - "cum": 250.0 + "maintMarginRatio": 0.0333, + "cum": 41.5 } }, { @@ -35812,37 +35831,71 @@ "symbol": "EWY/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 4, - "notionalCap": 100000, + "initialLeverage": 10, + "notionalCap": 25000, "notionalFloor": 10000, - "maintMarginRatio": 0.125, - "cum": 500.0 + "maintMarginRatio": 0.05, + "cum": 208.5 } }, { "tier": 4.0, "symbol": "EWY/USDT:USDT", "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 4, + "initialLeverage": 5, + "notionalCap": 50000, + "notionalFloor": 25000, + "maintMarginRatio": 0.1, + "cum": 1458.5 + } + }, + { + "tier": 5.0, + "symbol": "EWY/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 5, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 50000, + "maintMarginRatio": 0.125, + "cum": 2708.5 + } + }, + { + "tier": 6.0, + "symbol": "EWY/USDT:USDT", + "currency": "USDT", "minNotional": 100000.0, "maxNotional": 500000.0, "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 4, + "bracket": 6, "initialLeverage": 3, "notionalCap": 500000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 4670.0 + "cum": 6878.5 } }, { - "tier": 5.0, + "tier": 7.0, "symbol": "EWY/USDT:USDT", "currency": "USDT", "minNotional": 500000.0, @@ -35850,16 +35903,16 @@ "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 5, + "bracket": 7, "initialLeverage": 2, "notionalCap": 8000000, "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 46320.0 + "cum": 48528.5 } }, { - "tier": 6.0, + "tier": 8.0, "symbol": "EWY/USDT:USDT", "currency": "USDT", "minNotional": 8000000.0, @@ -35867,12 +35920,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 6, + "bracket": 8, "initialLeverage": 1, "notionalCap": 15000000, "notionalFloor": 8000000, "maintMarginRatio": 0.5, - "cum": 2046320.0 + "cum": 2048528.5 } } ], @@ -40004,14 +40057,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 20, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.025, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -40021,15 +40074,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 2, - "initialLeverage": 10, + "initialLeverage": 20, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.05, - "cum": 125.0 + "maintMarginRatio": 0.025, + "cum": 50.0 } }, { @@ -40037,50 +40090,50 @@ "symbol": "GENIUS/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 5, - "notionalCap": 50000, + "initialLeverage": 10, + "notionalCap": 20000, "notionalFloor": 10000, - "maintMarginRatio": 0.1, - "cum": 625.0 + "maintMarginRatio": 0.05, + "cum": 300.0 } }, { "tier": 4.0, "symbol": "GENIUS/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 20000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 4, - "initialLeverage": 4, - "notionalCap": 100000, - "notionalFloor": 50000, - "maintMarginRatio": 0.125, - "cum": 1875.0 + "initialLeverage": 5, + "notionalCap": 50000, + "notionalFloor": 20000, + "maintMarginRatio": 0.1, + "cum": 1300.0 } }, { "tier": 5.0, "symbol": "GENIUS/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, + "minNotional": 50000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 5, - "initialLeverage": 3, + "initialLeverage": 4, "notionalCap": 250000, - "notionalFloor": 100000, - "maintMarginRatio": 0.1667, - "cum": 6045.0 + "notionalFloor": 50000, + "maintMarginRatio": 0.125, + "cum": 2550.0 } }, { @@ -40089,15 +40142,15 @@ "currency": "USDT", "minNotional": 250000.0, "maxNotional": 500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 6, - "initialLeverage": 2, + "initialLeverage": 3, "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.25, - "cum": 26870.0 + "maintMarginRatio": 0.1667, + "cum": 12975.0 } }, { @@ -40105,16 +40158,33 @@ "symbol": "GENIUS/USDT:USDT", "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 800000.0, + "maxNotional": 7500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 7, + "initialLeverage": 2, + "notionalCap": 7500000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 54625.0 + } + }, + { + "tier": 8.0, + "symbol": "GENIUS/USDT:USDT", + "currency": "USDT", + "minNotional": 7500000.0, + "maxNotional": 12500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 7, + "bracket": 8, "initialLeverage": 1, - "notionalCap": 800000, - "notionalFloor": 500000, + "notionalCap": 12500000, + "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 151870.0 + "cum": 1929625.0 } } ], @@ -48125,14 +48195,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 40.0, + "maintenanceMarginRate": 0.04, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 40, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.02, + "maintMarginRatio": 0.04, "cum": 0.0 } }, @@ -48142,15 +48212,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 2, - "initialLeverage": 20, + "initialLeverage": 10, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.025, - "cum": 25.0 + "maintMarginRatio": 0.05, + "cum": 50.0 } }, { @@ -48158,37 +48228,20 @@ "symbol": "IR/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": 3, - "initialLeverage": 10, - "notionalCap": 20000, - "notionalFloor": 10000, - "maintMarginRatio": 0.05, - "cum": 275.0 - } - }, - { - "tier": 4.0, - "symbol": "IR/USDT:USDT", - "currency": "USDT", - "minNotional": 20000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": 4, + "bracket": 3, "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 20000, + "notionalFloor": 10000, "maintMarginRatio": 0.1, - "cum": 1275.0 + "cum": 550.0 } }, { - "tier": 5.0, + "tier": 4.0, "symbol": "IR/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, @@ -48196,16 +48249,16 @@ "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 5, + "bracket": 4, "initialLeverage": 4, "notionalCap": 100000, "notionalFloor": 50000, "maintMarginRatio": 0.125, - "cum": 2525.0 + "cum": 1800.0 } }, { - "tier": 6.0, + "tier": 5.0, "symbol": "IR/USDT:USDT", "currency": "USDT", "minNotional": 100000.0, @@ -48213,46 +48266,46 @@ "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 6, + "bracket": 5, "initialLeverage": 3, "notionalCap": 250000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 6695.0 + "cum": 5970.0 + } + }, + { + "tier": 6.0, + "symbol": "IR/USDT:USDT", + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 6, + "initialLeverage": 2, + "notionalCap": 400000, + "notionalFloor": 250000, + "maintMarginRatio": 0.25, + "cum": 26795.0 } }, { "tier": 7.0, "symbol": "IR/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 2500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 2500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.25, - "cum": 27520.0 - } - }, - { - "tier": 8.0, - "symbol": "IR/USDT:USDT", - "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 400000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 7, "initialLeverage": 1, - "notionalCap": 5000000, - "notionalFloor": 2500000, + "notionalCap": 500000, + "notionalFloor": 400000, "maintMarginRatio": 0.5, - "cum": 652520.0 + "cum": 126795.0 } } ], @@ -60097,6 +60150,110 @@ } } ], + "MSFT/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "MSFT/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 1, + "initialLeverage": 10, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.05, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "MSFT/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 2, + "initialLeverage": 5, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.1, + "cum": 250.0 + } + }, + { + "tier": 3.0, + "symbol": "MSFT/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 3, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 10000, + "maintMarginRatio": 0.125, + "cum": 500.0 + } + }, + { + "tier": 4.0, + "symbol": "MSFT/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 4, + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 4670.0 + } + }, + { + "tier": 5.0, + "symbol": "MSFT/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 8000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 5, + "initialLeverage": 2, + "notionalCap": 8000000, + "notionalFloor": 500000, + "maintMarginRatio": 0.25, + "cum": 46320.0 + } + }, + { + "tier": 6.0, + "symbol": "MSFT/USDT:USDT", + "currency": "USDT", + "minNotional": 8000000.0, + "maxNotional": 15000000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 6, + "initialLeverage": 1, + "notionalCap": 15000000, + "notionalFloor": 8000000, + "maintMarginRatio": 0.5, + "cum": 2046320.0 + } + } + ], "MSTR/USDT:USDT": [ { "tier": 1.0, @@ -65789,6 +65946,127 @@ } } ], + "OPG/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "OPG/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": 1, + "initialLeverage": 20, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.025, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "OPG/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 2, + "initialLeverage": 10, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.05, + "cum": 125.0 + } + }, + { + "tier": 3.0, + "symbol": "OPG/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 3, + "initialLeverage": 5, + "notionalCap": 50000, + "notionalFloor": 10000, + "maintMarginRatio": 0.1, + "cum": 625.0 + } + }, + { + "tier": 4.0, + "symbol": "OPG/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 4, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 50000, + "maintMarginRatio": 0.125, + "cum": 1875.0 + } + }, + { + "tier": 5.0, + "symbol": "OPG/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 5, + "initialLeverage": 3, + "notionalCap": 250000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 6045.0 + } + }, + { + "tier": 6.0, + "symbol": "OPG/USDT:USDT", + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 6, + "initialLeverage": 2, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.25, + "cum": 26870.0 + } + }, + { + "tier": 7.0, + "symbol": "OPG/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 7, + "initialLeverage": 1, + "notionalCap": 800000, + "notionalFloor": 500000, + "maintMarginRatio": 0.5, + "cum": 151870.0 + } + } + ], "OPN/USDT:USDT": [ { "tier": 1.0, @@ -69447,13 +69725,13 @@ "symbol": "PNUT/USDT:USDT", "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 4500000.0, + "maxNotional": 1000000.0, "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { "bracket": 8, "initialLeverage": 3, - "notionalCap": 4500000, + "notionalCap": 1000000, "notionalFloor": 500000, "maintMarginRatio": 0.1667, "cum": 34800.0 @@ -69463,7 +69741,7 @@ "tier": 9.0, "symbol": "PNUT/USDT:USDT", "currency": "USDT", - "minNotional": 4500000.0, + "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, @@ -69471,9 +69749,9 @@ "bracket": 9, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 4500000, + "notionalFloor": 1000000, "maintMarginRatio": 0.25, - "cum": 409650.0 + "cum": 118100.0 } }, { @@ -69490,7 +69768,7 @@ "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 2284650.0 + "cum": 1993100.0 } } ], @@ -72295,14 +72573,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 10, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.05, + "maintMarginRatio": 0.025, "cum": 0.0 } }, @@ -72312,15 +72590,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maintenanceMarginRate": 0.0333, + "maxLeverage": 15.0, "info": { "bracket": 2, - "initialLeverage": 5, + "initialLeverage": 15, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.1, - "cum": 250.0 + "maintMarginRatio": 0.0333, + "cum": 41.5 } }, { @@ -72328,37 +72606,71 @@ "symbol": "QQQ/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 4, - "notionalCap": 100000, + "initialLeverage": 10, + "notionalCap": 25000, "notionalFloor": 10000, - "maintMarginRatio": 0.125, - "cum": 500.0 + "maintMarginRatio": 0.05, + "cum": 208.5 } }, { "tier": 4.0, "symbol": "QQQ/USDT:USDT", "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 4, + "initialLeverage": 5, + "notionalCap": 50000, + "notionalFloor": 25000, + "maintMarginRatio": 0.1, + "cum": 1458.5 + } + }, + { + "tier": 5.0, + "symbol": "QQQ/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 5, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 50000, + "maintMarginRatio": 0.125, + "cum": 2708.5 + } + }, + { + "tier": 6.0, + "symbol": "QQQ/USDT:USDT", + "currency": "USDT", "minNotional": 100000.0, "maxNotional": 500000.0, "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 4, + "bracket": 6, "initialLeverage": 3, "notionalCap": 500000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 4670.0 + "cum": 6878.5 } }, { - "tier": 5.0, + "tier": 7.0, "symbol": "QQQ/USDT:USDT", "currency": "USDT", "minNotional": 500000.0, @@ -72366,16 +72678,16 @@ "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 5, + "bracket": 7, "initialLeverage": 2, "notionalCap": 8000000, "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 46320.0 + "cum": 48528.5 } }, { - "tier": 6.0, + "tier": 8.0, "symbol": "QQQ/USDT:USDT", "currency": "USDT", "minNotional": 8000000.0, @@ -72383,12 +72695,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 6, + "bracket": 8, "initialLeverage": 1, "notionalCap": 15000000, "notionalFloor": 8000000, "maintMarginRatio": 0.5, - "cum": 2046320.0 + "cum": 2048528.5 } } ], @@ -72934,14 +73246,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.025, + "maintenanceMarginRate": 0.04, "maxLeverage": 20.0, "info": { "bracket": 1, "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.025, + "maintMarginRatio": 0.04, "cum": 0.0 } }, @@ -72959,7 +73271,7 @@ "notionalCap": 10000, "notionalFloor": 5000, "maintMarginRatio": 0.05, - "cum": 125.0 + "cum": 50.0 } }, { @@ -72976,7 +73288,7 @@ "notionalCap": 50000, "notionalFloor": 10000, "maintMarginRatio": 0.1, - "cum": 625.0 + "cum": 550.0 } }, { @@ -72993,7 +73305,7 @@ "notionalCap": 100000, "notionalFloor": 50000, "maintMarginRatio": 0.125, - "cum": 1875.0 + "cum": 1800.0 } }, { @@ -73010,7 +73322,7 @@ "notionalCap": 250000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 6045.0 + "cum": 5970.0 } }, { @@ -73027,7 +73339,7 @@ "notionalCap": 500000, "notionalFloor": 250000, "maintMarginRatio": 0.25, - "cum": 26870.0 + "cum": 26795.0 } }, { @@ -73044,7 +73356,7 @@ "notionalCap": 800000, "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 151870.0 + "cum": 151795.0 } } ], @@ -81830,14 +82142,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 10, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.05, + "maintMarginRatio": 0.025, "cum": 0.0 } }, @@ -81847,15 +82159,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maintenanceMarginRate": 0.0333, + "maxLeverage": 15.0, "info": { "bracket": 2, - "initialLeverage": 5, + "initialLeverage": 15, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.1, - "cum": 250.0 + "maintMarginRatio": 0.0333, + "cum": 41.5 } }, { @@ -81863,37 +82175,71 @@ "symbol": "SPY/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maxNotional": 25000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 4, - "notionalCap": 100000, + "initialLeverage": 10, + "notionalCap": 25000, "notionalFloor": 10000, - "maintMarginRatio": 0.125, - "cum": 500.0 + "maintMarginRatio": 0.05, + "cum": 208.5 } }, { "tier": 4.0, "symbol": "SPY/USDT:USDT", "currency": "USDT", + "minNotional": 25000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 4, + "initialLeverage": 5, + "notionalCap": 50000, + "notionalFloor": 25000, + "maintMarginRatio": 0.1, + "cum": 1458.5 + } + }, + { + "tier": 5.0, + "symbol": "SPY/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 5, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 50000, + "maintMarginRatio": 0.125, + "cum": 2708.5 + } + }, + { + "tier": 6.0, + "symbol": "SPY/USDT:USDT", + "currency": "USDT", "minNotional": 100000.0, "maxNotional": 500000.0, "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 4, + "bracket": 6, "initialLeverage": 3, "notionalCap": 500000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 4670.0 + "cum": 6878.5 } }, { - "tier": 5.0, + "tier": 7.0, "symbol": "SPY/USDT:USDT", "currency": "USDT", "minNotional": 500000.0, @@ -81901,16 +82247,16 @@ "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 5, + "bracket": 7, "initialLeverage": 2, "notionalCap": 8000000, "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 46320.0 + "cum": 48528.5 } }, { - "tier": 6.0, + "tier": 8.0, "symbol": "SPY/USDT:USDT", "currency": "USDT", "minNotional": 8000000.0, @@ -81918,12 +82264,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 6, + "bracket": 8, "initialLeverage": 1, "notionalCap": 15000000, "notionalFloor": 8000000, "maintMarginRatio": 0.5, - "cum": 2046320.0 + "cum": 2048528.5 } } ], @@ -101085,14 +101431,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 40.0, + "maintenanceMarginRate": 0.04, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 40, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.02, + "maintMarginRatio": 0.04, "cum": 0.0 } }, @@ -101102,15 +101448,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 2, - "initialLeverage": 20, + "initialLeverage": 10, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.025, - "cum": 25.0 + "maintMarginRatio": 0.05, + "cum": 50.0 } }, { @@ -101118,37 +101464,20 @@ "symbol": "ZKJ/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": 3, - "initialLeverage": 10, - "notionalCap": 20000, - "notionalFloor": 10000, - "maintMarginRatio": 0.05, - "cum": 275.0 - } - }, - { - "tier": 4.0, - "symbol": "ZKJ/USDT:USDT", - "currency": "USDT", - "minNotional": 20000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": 4, + "bracket": 3, "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 20000, + "notionalFloor": 10000, "maintMarginRatio": 0.1, - "cum": 1275.0 + "cum": 550.0 } }, { - "tier": 5.0, + "tier": 4.0, "symbol": "ZKJ/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, @@ -101156,16 +101485,16 @@ "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 5, + "bracket": 4, "initialLeverage": 4, "notionalCap": 100000, "notionalFloor": 50000, "maintMarginRatio": 0.125, - "cum": 2525.0 + "cum": 1800.0 } }, { - "tier": 6.0, + "tier": 5.0, "symbol": "ZKJ/USDT:USDT", "currency": "USDT", "minNotional": 100000.0, @@ -101173,46 +101502,46 @@ "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 6, + "bracket": 5, "initialLeverage": 3, "notionalCap": 250000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 6695.0 + "cum": 5970.0 + } + }, + { + "tier": 6.0, + "symbol": "ZKJ/USDT:USDT", + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 400000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 6, + "initialLeverage": 2, + "notionalCap": 400000, + "notionalFloor": 250000, + "maintMarginRatio": 0.25, + "cum": 26795.0 } }, { "tier": 7.0, "symbol": "ZKJ/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 2500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 2500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.25, - "cum": 27520.0 - } - }, - { - "tier": 8.0, - "symbol": "ZKJ/USDT:USDT", - "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 400000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 7, "initialLeverage": 1, - "notionalCap": 5000000, - "notionalFloor": 2500000, + "notionalCap": 500000, + "notionalFloor": 400000, "maintMarginRatio": 0.5, - "cum": 652520.0 + "cum": 126795.0 } } ], @@ -101980,15 +102309,15 @@ "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.04, + "maxNotional": 2000.0, + "maintenanceMarginRate": 0.045, "maxLeverage": 20.0, "info": { "bracket": 1, "initialLeverage": 20, - "notionalCap": 5000, + "notionalCap": 2000, "notionalFloor": 0, - "maintMarginRatio": 0.04, + "maintMarginRatio": 0.045, "cum": 0.0 } }, @@ -101996,7 +102325,7 @@ "tier": 2.0, "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, + "minNotional": 2000.0, "maxNotional": 15000.0, "maintenanceMarginRate": 0.05, "maxLeverage": 10.0, @@ -102004,9 +102333,9 @@ "bracket": 2, "initialLeverage": 10, "notionalCap": 15000, - "notionalFloor": 5000, + "notionalFloor": 2000, "maintMarginRatio": 0.05, - "cum": 50.0 + "cum": 10.0 } }, { @@ -102023,7 +102352,7 @@ "notionalCap": 60000, "notionalFloor": 15000, "maintMarginRatio": 0.1, - "cum": 800.0 + "cum": 760.0 } }, { @@ -102040,7 +102369,7 @@ "notionalCap": 200000, "notionalFloor": 60000, "maintMarginRatio": 0.125, - "cum": 2300.0 + "cum": 2260.0 } }, { @@ -102048,50 +102377,50 @@ "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", "minNotional": 200000.0, - "maxNotional": 500000.0, + "maxNotional": 400000.0, "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { "bracket": 5, "initialLeverage": 3, - "notionalCap": 500000, + "notionalCap": 400000, "notionalFloor": 200000, "maintMarginRatio": 0.1667, - "cum": 10640.0 + "cum": 10600.0 } }, { "tier": 6.0, "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 2500000.0, + "minNotional": 400000.0, + "maxNotional": 1200000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { "bracket": 6, "initialLeverage": 2, - "notionalCap": 2500000, - "notionalFloor": 500000, + "notionalCap": 1200000, + "notionalFloor": 400000, "maintMarginRatio": 0.25, - "cum": 52290.0 + "cum": 43920.0 } }, { "tier": 7.0, "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 1200000.0, + "maxNotional": 4000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { "bracket": 7, "initialLeverage": 1, - "notionalCap": 5000000, - "notionalFloor": 2500000, + "notionalCap": 4000000, + "notionalFloor": 1200000, "maintMarginRatio": 0.5, - "cum": 677290.0 + "cum": 343920.0 } } ], @@ -102222,15 +102551,15 @@ "symbol": "\u9f99\u867e/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 1, - "initialLeverage": 5, - "notionalCap": 5000, + "initialLeverage": 10, + "notionalCap": 10000, "notionalFloor": 0, - "maintMarginRatio": 0.1, + "maintMarginRatio": 0.05, "cum": 0.0 } }, @@ -102238,68 +102567,85 @@ "tier": 2.0, "symbol": "\u9f99\u867e/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 2, - "initialLeverage": 4, - "notionalCap": 10000, - "notionalFloor": 5000, - "maintMarginRatio": 0.125, - "cum": 125.0 + "initialLeverage": 5, + "notionalCap": 50000, + "notionalFloor": 10000, + "maintMarginRatio": 0.1, + "cum": 500.0 } }, { "tier": 3.0, "symbol": "\u9f99\u867e/USDT:USDT", "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 30000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 3, - "initialLeverage": 3, - "notionalCap": 30000, - "notionalFloor": 10000, - "maintMarginRatio": 0.1667, - "cum": 542.0 + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 50000, + "maintMarginRatio": 0.125, + "cum": 1750.0 } }, { "tier": 4.0, "symbol": "\u9f99\u867e/USDT:USDT", "currency": "USDT", - "minNotional": 30000.0, - "maxNotional": 80000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 4, - "initialLeverage": 2, - "notionalCap": 80000, - "notionalFloor": 30000, - "maintMarginRatio": 0.25, - "cum": 3041.0 + "initialLeverage": 3, + "notionalCap": 250000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 5920.0 } }, { "tier": 5.0, "symbol": "\u9f99\u867e/USDT:USDT", "currency": "USDT", - "minNotional": 80000.0, - "maxNotional": 200000.0, + "minNotional": 250000.0, + "maxNotional": 2500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 5, + "initialLeverage": 2, + "notionalCap": 2500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.25, + "cum": 26745.0 + } + }, + { + "tier": 6.0, + "symbol": "\u9f99\u867e/USDT:USDT", + "currency": "USDT", + "minNotional": 2500000.0, + "maxNotional": 5000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 5, + "bracket": 6, "initialLeverage": 1, - "notionalCap": 200000, - "notionalFloor": 80000, + "notionalCap": 5000000, + "notionalFloor": 2500000, "maintMarginRatio": 0.5, - "cum": 23041.0 + "cum": 651745.0 } } ] From a625cbecff7930682b2692edac824bfcb18d7726 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 23 Apr 2026 20:30:31 +0200 Subject: [PATCH 279/315] chore: improve shutdown for exchange_ws --- freqtrade/exchange/exchange_ws.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/exchange/exchange_ws.py b/freqtrade/exchange/exchange_ws.py index cbc9772a3..250a9c5c5 100644 --- a/freqtrade/exchange/exchange_ws.py +++ b/freqtrade/exchange/exchange_ws.py @@ -31,7 +31,6 @@ class ExchangeWS: self.klines_last_request: dict[PairWithTimeframe, float] = {} self._thread = Thread(name="ccxt_ws", target=self._start_forever) self._thread.start() - self.__cleanup_called = False def _start_forever(self) -> None: self._loop = asyncio.new_event_loop() @@ -63,10 +62,13 @@ class ExchangeWS: """ if hasattr(self, "_loop") and not self._loop.is_closed(): logger.info("Resetting WS connections.") - asyncio.run_coroutine_threadsafe(self._cleanup_async(), loop=self._loop) - while not self.__cleanup_called: - time.sleep(0.1) - self.__cleanup_called = False + try: + fut = asyncio.run_coroutine_threadsafe(self._cleanup_async(), loop=self._loop) + fut.result(timeout=10) + except TimeoutError: + logger.warning("Timed out while resetting websocket connections.") + except Exception: + logger.exception("Exception while resetting websocket connections") async def _cleanup_async(self) -> None: try: @@ -76,8 +78,6 @@ class ExchangeWS: self._ccxt_object.ohlcvs.clear() except Exception: logger.exception("Exception in _cleanup_async") - finally: - self.__cleanup_called = True def _pop_history(self, paircomb: PairWithTimeframe) -> None: """ From cdc01122d502c36f7dda3c79c487c18954c36ed4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 23 Apr 2026 20:51:38 +0200 Subject: [PATCH 280/315] chore: improve ws shutdown --- freqtrade/exchange/exchange_ws.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/freqtrade/exchange/exchange_ws.py b/freqtrade/exchange/exchange_ws.py index 250a9c5c5..f9c7f48f3 100644 --- a/freqtrade/exchange/exchange_ws.py +++ b/freqtrade/exchange/exchange_ws.py @@ -1,6 +1,5 @@ import asyncio import logging -import time from copy import deepcopy from functools import partial from threading import Thread @@ -37,8 +36,15 @@ class ExchangeWS: try: self._loop.run_forever() finally: - if self._loop.is_running(): - self._loop.stop() + if not self._loop.is_closed(): + # Cancel remaining tasks and close the loop in the owning thread. + pending = asyncio.all_tasks(self._loop) + for task in pending: + task.cancel() + if pending: + self._loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) + self._loop.run_until_complete(self._loop.shutdown_asyncgens()) + self._loop.close() def cleanup(self) -> None: logger.debug("Cleanup called - stopping") @@ -47,13 +53,10 @@ class ExchangeWS: task.cancel() if hasattr(self, "_loop") and not self._loop.is_closed(): self.reset_connections() - self._loop.call_soon_threadsafe(self._loop.stop) - time.sleep(0.1) - if not self._loop.is_closed(): - self._loop.close() - - self._thread.join() + self._thread.join(timeout=5) + if self._thread.is_alive(): + logger.warning("Websocket loop thread did not stop within timeout.") logger.debug("Stopped") def reset_connections(self) -> None: From 8e9d19379124c0d201c83912f69fec2dfe0b428a Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 23 Apr 2026 21:10:30 +0200 Subject: [PATCH 281/315] chore: improved shutdown wording --- freqtrade/exchange/exchange_ws.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/exchange_ws.py b/freqtrade/exchange/exchange_ws.py index f9c7f48f3..be78e31fa 100644 --- a/freqtrade/exchange/exchange_ws.py +++ b/freqtrade/exchange/exchange_ws.py @@ -52,19 +52,19 @@ class ExchangeWS: for task in self._background_tasks: task.cancel() if hasattr(self, "_loop") and not self._loop.is_closed(): - self.reset_connections() + self.reset_connections(cleanup=True) self._loop.call_soon_threadsafe(self._loop.stop) self._thread.join(timeout=5) if self._thread.is_alive(): logger.warning("Websocket loop thread did not stop within timeout.") logger.debug("Stopped") - def reset_connections(self) -> None: + def reset_connections(self, cleanup: bool = False) -> None: """ Reset all connections - avoids "connection-reset" errors that happen after ~9 days """ if hasattr(self, "_loop") and not self._loop.is_closed(): - logger.info("Resetting WS connections.") + logger.info(f"{'Cleaning up' if cleanup else 'Resetting'} exchange WS connections.") try: fut = asyncio.run_coroutine_threadsafe(self._cleanup_async(), loop=self._loop) fut.result(timeout=10) @@ -149,7 +149,7 @@ class ExchangeWS: logger.debug("un_watch_ohlcv_for_symbols not supported: %s", e) pass except Exception: - logger.exception("Exception in _unwatch_ohlcv") + logger.exception(f"Exception in _unwatch_ohlcv for {pair}, {timeframe},") def _continuous_stopped( self, task: asyncio.Task, pair: str, timeframe: str, candle_type: CandleType From f9be1ac82c0fde3ebff60a1a02ff63dafa2111dd Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 24 Apr 2026 06:33:04 +0200 Subject: [PATCH 282/315] chore: ignore ws shutdown network errors --- freqtrade/exchange/exchange_ws.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/exchange/exchange_ws.py b/freqtrade/exchange/exchange_ws.py index be78e31fa..f99c6aebe 100644 --- a/freqtrade/exchange/exchange_ws.py +++ b/freqtrade/exchange/exchange_ws.py @@ -148,6 +148,10 @@ class ExchangeWS: except ccxt.NotSupported as e: logger.debug("un_watch_ohlcv_for_symbols not supported: %s", e) pass + except ccxt.NetworkError as e: + # Network errors are common on shutdown so we can ignore them. + # It's a network error - which most likely means that the connection is already closed. + logger.debug("Network error during unwatch for %s, %s: %s", pair, timeframe, e) except Exception: logger.exception(f"Exception in _unwatch_ohlcv for {pair}, {timeframe},") From 05c336089273a1468a51586f6e6eaba871b811d7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 24 Apr 2026 06:37:05 +0200 Subject: [PATCH 283/315] chore: harden ws stop cleanup --- freqtrade/exchange/exchange_ws.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/freqtrade/exchange/exchange_ws.py b/freqtrade/exchange/exchange_ws.py index f99c6aebe..7620f08b4 100644 --- a/freqtrade/exchange/exchange_ws.py +++ b/freqtrade/exchange/exchange_ws.py @@ -157,22 +157,27 @@ class ExchangeWS: def _continuous_stopped( self, task: asyncio.Task, pair: str, timeframe: str, candle_type: CandleType - ): + ) -> None: self._background_tasks.discard(task) result = "done" - if task.cancelled(): - result = "cancelled" - else: - if (result1 := task.result()) is not None: - result = str(result1) + try: + if task.cancelled(): + result = "cancelled" + else: + if (result1 := task.result()) is not None: + result = str(result1) + except Exception: + result = "error" + logger.exception(f"Unhandled exception in watch task callback for {pair}, {timeframe}") + finally: + logger.info(f"{pair}, {timeframe}, {candle_type} - Task finished - {result}") + if hasattr(self, "_loop") and not self._loop.is_closed(): + asyncio.run_coroutine_threadsafe( + self._unwatch_ohlcv(pair, timeframe, candle_type), loop=self._loop + ) - logger.info(f"{pair}, {timeframe}, {candle_type} - Task finished - {result}") - asyncio.run_coroutine_threadsafe( - self._unwatch_ohlcv(pair, timeframe, candle_type), loop=self._loop - ) - - self._klines_scheduled.discard((pair, timeframe, candle_type)) - self._pop_history((pair, timeframe, candle_type)) + self._klines_scheduled.discard((pair, timeframe, candle_type)) + self._pop_history((pair, timeframe, candle_type)) async def _continuously_async_watch_ohlcv( self, pair: str, timeframe: str, candle_type: CandleType From 6d096c20b0eca63a75994c18a5b80735ef25b1c3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 24 Apr 2026 07:06:07 +0200 Subject: [PATCH 284/315] test: add test for exchange_ws reset connection details --- tests/exchange/test_exchange_ws.py | 61 ++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/tests/exchange/test_exchange_ws.py b/tests/exchange/test_exchange_ws.py index 2cc4873fe..5663ba434 100644 --- a/tests/exchange/test_exchange_ws.py +++ b/tests/exchange/test_exchange_ws.py @@ -49,6 +49,67 @@ def test_exchangews_cleanup_error(mocker, caplog): exchange_ws.cleanup() +def test_exchangews_reset_connections_timeout_and_exception(mocker, caplog): + config = MagicMock() + ccxt_object = MagicMock() + mocker.patch("freqtrade.exchange.exchange_ws.ExchangeWS._start_forever", MagicMock()) + + exchange_ws = ExchangeWS(config, ccxt_object) + exchange_ws._loop = MagicMock() + exchange_ws._loop.is_closed.return_value = False + + timeout_future = MagicMock() + timeout_future.result.side_effect = TimeoutError("timed out") + + error_future = MagicMock() + error_future.result.side_effect = RuntimeError("broken future") + + def fake_run_coroutine_threadsafe(coro, loop): + # Avoid coroutine warnings since we don't execute it in this unit test. + coro.close() + fake_run_coroutine_threadsafe.calls += 1 + return timeout_future if fake_run_coroutine_threadsafe.calls == 1 else error_future + + fake_run_coroutine_threadsafe.calls = 0 + + mock_run = mocker.patch( + "freqtrade.exchange.exchange_ws.asyncio.run_coroutine_threadsafe", + side_effect=fake_run_coroutine_threadsafe, + ) + + exchange_ws.reset_connections() + assert log_has_re("Timed out while resetting websocket connections", caplog) + assert log_has_re("Resetting exchange WS connections", caplog) + assert mock_run.call_count == 1 + + exchange_ws.reset_connections(cleanup=True) + + assert mock_run.call_count == 2 + assert log_has_re("Exception while resetting websocket connections", caplog) + assert log_has_re("Cleaning up exchange WS connections", caplog) + + exchange_ws.cleanup() + + +def test_exchangews_cleanup_thread_timeout_warning(mocker, caplog): + config = MagicMock() + ccxt_object = MagicMock() + mocker.patch("freqtrade.exchange.exchange_ws.ExchangeWS._start_forever", MagicMock()) + + exchange_ws = ExchangeWS(config, ccxt_object) + exchange_ws._loop = MagicMock() + exchange_ws._loop.is_closed.return_value = True + + thread_mock = MagicMock() + thread_mock.is_alive.return_value = True + exchange_ws._thread = thread_mock + + exchange_ws.cleanup() + + thread_mock.join.assert_called_once_with(timeout=5) + assert log_has_re("Websocket loop thread did not stop within timeout", caplog) + + def patch_eventloop_threading(exchange): init_event = threading.Event() From 9d0fb9b0257dad05f19c153d624f74e0ac931647 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 24 Apr 2026 07:08:48 +0200 Subject: [PATCH 285/315] test: add explicit test for continuous_stopped handling --- tests/exchange/test_exchange_ws.py | 51 ++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tests/exchange/test_exchange_ws.py b/tests/exchange/test_exchange_ws.py index 5663ba434..d9799a3e7 100644 --- a/tests/exchange/test_exchange_ws.py +++ b/tests/exchange/test_exchange_ws.py @@ -289,3 +289,54 @@ async def test_exchangews_get_ohlcv(mocker, caplog): assert log_has_re(msg, caplog) exchange_ws.cleanup() + + +def test_exchangews_continuous_stopped_task_exception(mocker, caplog): + config = MagicMock() + ccxt_object = MagicMock() + ccxt_object.ohlcvs = { + "ETH/USDT": { + "1m": [ + [1635840000000, 100, 200, 300, 400, 500], + [1635840060000, 101, 201, 301, 401, 501], + [1635840120000, 102, 202, 302, 402, 502], + ] + } + } + mocker.patch("freqtrade.exchange.exchange_ws.ExchangeWS._start_forever", MagicMock()) + + exchange_ws = ExchangeWS(config, ccxt_object) + exchange_ws._loop = MagicMock() + exchange_ws._loop.is_closed.return_value = False + + paircomb = ("ETH/USDT", "1m", CandleType.SPOT) + exchange_ws._klines_scheduled.add(paircomb) + exchange_ws.klines_last_refresh[paircomb] = 1 + + task = MagicMock() + task.cancelled.return_value = False + task.result.side_effect = RuntimeError("unexpected") + exchange_ws._background_tasks.add(task) + + completed_future = MagicMock() + completed_future.result.return_value = None + + def side_effect(coro, loop): + coro.close() + return completed_future + + run_threadsafe = mocker.patch( + "freqtrade.exchange.exchange_ws.asyncio.run_coroutine_threadsafe", + side_effect=side_effect, + ) + + exchange_ws._continuous_stopped(task, "ETH/USDT", "1m", CandleType.SPOT) + + assert task not in exchange_ws._background_tasks + assert paircomb not in exchange_ws._klines_scheduled + assert paircomb not in exchange_ws.klines_last_refresh + assert ccxt_object.ohlcvs["ETH/USDT"].get("1m") is None + assert run_threadsafe.call_count == 1 + assert log_has_re("Unhandled exception in watch task callback for ETH/USDT, 1m", caplog) + + exchange_ws.cleanup() From af1de46cd4dd968482dee15a5deebd57005d8691 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 24 Apr 2026 18:24:31 +0200 Subject: [PATCH 286/315] chore: fix minor typos --- freqtrade/data/metrics.py | 2 +- freqtrade/persistence/trade_model.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/data/metrics.py b/freqtrade/data/metrics.py index 4d66104ea..0d7c65bab 100644 --- a/freqtrade/data/metrics.py +++ b/freqtrade/data/metrics.py @@ -140,7 +140,7 @@ def _calc_drawdown_series( max_drawdown_df["drawdown_relative"] = (max_balance - cumulative_balance) / max_balance else: # NOTE: This is not completely accurate, - # but might good enough if starting_balance is not available + # but will be 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"] diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index e6107eac6..7e8dd2e3c 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -858,9 +858,9 @@ class LocalTrade: higher_stop = stop_loss_norm > self.stop_loss lower_stop = stop_loss_norm < self.stop_loss - # stop losses only walk up, never down!, - # ? But adding more to a leveraged trade would create a lower liquidation price, - # ? decreasing the minimum stoploss + # stop losses only walk up, never down! + # but adding more to a leveraged trade would create a lower liquidation price, + # decreasing the minimum stoploss if ( allow_refresh or (higher_stop and not self.is_short) From 3949efadfe5bb4b20236dd844e9546c2210a0585 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Apr 2026 08:37:09 +0200 Subject: [PATCH 287/315] chore: improve exchange_ws startup safety --- freqtrade/exchange/exchange_ws.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/exchange_ws.py b/freqtrade/exchange/exchange_ws.py index 7620f08b4..caccf6b45 100644 --- a/freqtrade/exchange/exchange_ws.py +++ b/freqtrade/exchange/exchange_ws.py @@ -2,7 +2,7 @@ import asyncio import logging from copy import deepcopy from functools import partial -from threading import Thread +from threading import Event, Thread import ccxt @@ -23,6 +23,7 @@ class ExchangeWS: self.config = config self._ccxt_object = ccxt_object self._background_tasks: set[asyncio.Task] = set() + self._loop_ready = Event() self._klines_watching: set[PairWithTimeframe] = set() self._klines_scheduled: set[PairWithTimeframe] = set() @@ -33,6 +34,7 @@ class ExchangeWS: def _start_forever(self) -> None: self._loop = asyncio.new_event_loop() + self._loop_ready.set() try: self._loop.run_forever() finally: @@ -45,13 +47,24 @@ class ExchangeWS: self._loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) self._loop.run_until_complete(self._loop.shutdown_asyncgens()) self._loop.close() + self._loop_ready.clear() + + def _wait_for_loop(self, timeout: float = 1.0) -> bool: + """ + Wait for the event loop to be ready + Returns True once the loop is ready. + Will probably only return false during startup/shutdown. + """ + if hasattr(self, "_loop"): + return True + return self._loop_ready.wait(timeout=timeout) and hasattr(self, "_loop") def cleanup(self) -> None: logger.debug("Cleanup called - stopping") self._klines_watching.clear() for task in self._background_tasks: task.cancel() - if hasattr(self, "_loop") and not self._loop.is_closed(): + if self._wait_for_loop(timeout=0.2) and not self._loop.is_closed(): self.reset_connections(cleanup=True) self._loop.call_soon_threadsafe(self._loop.stop) self._thread.join(timeout=5) @@ -63,7 +76,7 @@ class ExchangeWS: """ Reset all connections - avoids "connection-reset" errors that happen after ~9 days """ - if hasattr(self, "_loop") and not self._loop.is_closed(): + if self._wait_for_loop() and not self._loop.is_closed(): logger.info(f"{'Cleaning up' if cleanup else 'Resetting'} exchange WS connections.") try: fut = asyncio.run_coroutine_threadsafe(self._cleanup_async(), loop=self._loop) @@ -202,6 +215,9 @@ class ExchangeWS: """ Schedule a pair/timeframe combination to be watched """ + if not self._wait_for_loop(): + logger.warning(f"Websocket loop not ready. Could not schedule {pair}, {timeframe}.") + return self._klines_watching.add((pair, timeframe, candle_type)) self.klines_last_request[(pair, timeframe, candle_type)] = dt_ts() # asyncio.run_coroutine_threadsafe(self.schedule_schedule(), loop=self._loop) From 5e624d658fc49fd91f0893e0ecd1af7d57de3c5d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Apr 2026 08:37:19 +0200 Subject: [PATCH 288/315] test: add test for startup concurrency --- tests/exchange/test_exchange_ws.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/exchange/test_exchange_ws.py b/tests/exchange/test_exchange_ws.py index d9799a3e7..5071d52af 100644 --- a/tests/exchange/test_exchange_ws.py +++ b/tests/exchange/test_exchange_ws.py @@ -110,6 +110,23 @@ def test_exchangews_cleanup_thread_timeout_warning(mocker, caplog): assert log_has_re("Websocket loop thread did not stop within timeout", caplog) +def test_exchangews_schedule_ohlcv_loop_not_ready(mocker, caplog): + config = MagicMock() + ccxt_object = MagicMock() + mocker.patch("freqtrade.exchange.exchange_ws.ExchangeWS._start_forever", MagicMock()) + run_threadsafe = mocker.patch("freqtrade.exchange.exchange_ws.asyncio.run_coroutine_threadsafe") + + exchange_ws = ExchangeWS(config, ccxt_object) + exchange_ws.schedule_ohlcv("ETH/BTC", "1m", CandleType.SPOT) + + assert exchange_ws._klines_watching == set() + assert exchange_ws.klines_last_request == {} + assert run_threadsafe.call_count == 0 + assert log_has_re("Websocket loop not ready. Could not schedule ETH/BTC, 1m", caplog) + + exchange_ws.cleanup() + + def patch_eventloop_threading(exchange): init_event = threading.Event() From 0858b82d3e3ee405e992bac13f68dd62f72b6105 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Apr 2026 08:46:49 +0200 Subject: [PATCH 289/315] chore: add locks to exchange_ws key variables --- freqtrade/exchange/exchange_ws.py | 94 +++++++++++++++++++------------ 1 file changed, 57 insertions(+), 37 deletions(-) diff --git a/freqtrade/exchange/exchange_ws.py b/freqtrade/exchange/exchange_ws.py index caccf6b45..147541895 100644 --- a/freqtrade/exchange/exchange_ws.py +++ b/freqtrade/exchange/exchange_ws.py @@ -2,7 +2,7 @@ import asyncio import logging from copy import deepcopy from functools import partial -from threading import Event, Thread +from threading import Event, RLock, Thread import ccxt @@ -23,6 +23,7 @@ class ExchangeWS: self.config = config self._ccxt_object = ccxt_object self._background_tasks: set[asyncio.Task] = set() + self._state_lock = RLock() self._loop_ready = Event() self._klines_watching: set[PairWithTimeframe] = set() @@ -61,8 +62,10 @@ class ExchangeWS: def cleanup(self) -> None: logger.debug("Cleanup called - stopping") - self._klines_watching.clear() - for task in self._background_tasks: + with self._state_lock: + self._klines_watching.clear() + tasks = list(self._background_tasks) + for task in tasks: task.cancel() if self._wait_for_loop(timeout=0.2) and not self._loop.is_closed(): self.reset_connections(cleanup=True) @@ -99,8 +102,9 @@ class ExchangeWS: """ Remove history for a pair/timeframe combination from ccxt cache """ - self._ccxt_object.ohlcvs.get(paircomb[0], {}).pop(paircomb[1], None) - self.klines_last_refresh.pop(paircomb, None) + with self._state_lock: + self._ccxt_object.ohlcvs.get(paircomb[0], {}).pop(paircomb[1], None) + self.klines_last_refresh.pop(paircomb, None) @retrier(retries=3) def ohlcvs(self, pair: str, timeframe: str) -> list[list]: @@ -122,38 +126,45 @@ class ExchangeWS: the last timeframe (+ offset) """ changed = False - for p in list(self._klines_watching): - _, timeframe, _ = p - timeframe_s = timeframe_to_seconds(timeframe) - last_refresh = self.klines_last_request.get(p, 0) - if last_refresh > 0 and (dt_ts() - last_refresh) > ((timeframe_s + 20) * 1000): - logger.info(f"Removing {p} from websocket watchlist.") - self._klines_watching.discard(p) - # Pop history to avoid getting stale data - self._pop_history(p) - changed = True + with self._state_lock: + for p in list(self._klines_watching): + _, timeframe, _ = p + timeframe_s = timeframe_to_seconds(timeframe) + last_refresh = self.klines_last_request.get(p, 0) + if last_refresh > 0 and (dt_ts() - last_refresh) > ((timeframe_s + 20) * 1000): + logger.info(f"Removing {p} from websocket watchlist.") + self._klines_watching.discard(p) + # Pop history to avoid getting stale data + self._pop_history(p) + changed = True if changed: logger.info(f"Removal done: new watch list ({len(self._klines_watching)})") async def _schedule_while_true(self) -> None: # For the ones we should be watching - for p in self._klines_watching: + with self._state_lock: + pairs_to_check = list(self._klines_watching) + + for p in pairs_to_check: # Check if they're already scheduled - if p not in self._klines_scheduled: + with self._state_lock: + if p in self._klines_scheduled: + continue self._klines_scheduled.add(p) - pair, timeframe, candle_type = p - task = asyncio.create_task( - self._continuously_async_watch_ohlcv(pair, timeframe, candle_type) - ) + pair, timeframe, candle_type = p + task = asyncio.create_task( + self._continuously_async_watch_ohlcv(pair, timeframe, candle_type) + ) + with self._state_lock: self._background_tasks.add(task) - task.add_done_callback( - partial( - self._continuous_stopped, - pair=pair, - timeframe=timeframe, - candle_type=candle_type, - ) + task.add_done_callback( + partial( + self._continuous_stopped, + pair=pair, + timeframe=timeframe, + candle_type=candle_type, ) + ) async def _unwatch_ohlcv(self, pair: str, timeframe: str, candle_type: CandleType) -> None: try: @@ -171,7 +182,8 @@ class ExchangeWS: def _continuous_stopped( self, task: asyncio.Task, pair: str, timeframe: str, candle_type: CandleType ) -> None: - self._background_tasks.discard(task) + with self._state_lock: + self._background_tasks.discard(task) result = "done" try: if task.cancelled(): @@ -189,17 +201,22 @@ class ExchangeWS: self._unwatch_ohlcv(pair, timeframe, candle_type), loop=self._loop ) - self._klines_scheduled.discard((pair, timeframe, candle_type)) + with self._state_lock: + self._klines_scheduled.discard((pair, timeframe, candle_type)) self._pop_history((pair, timeframe, candle_type)) async def _continuously_async_watch_ohlcv( self, pair: str, timeframe: str, candle_type: CandleType ) -> None: try: - while (pair, timeframe, candle_type) in self._klines_watching: + while True: + with self._state_lock: + if (pair, timeframe, candle_type) not in self._klines_watching: + break start = dt_ts() data = await self._ccxt_object.watch_ohlcv(pair, timeframe) - self.klines_last_refresh[(pair, timeframe, candle_type)] = dt_ts() + with self._state_lock: + self.klines_last_refresh[(pair, timeframe, candle_type)] = dt_ts() logger.debug( f"watch done {pair}, {timeframe}, data {len(data)} " f"in {(dt_ts() - start) / 1000:.3f}s" @@ -209,7 +226,8 @@ class ExchangeWS: except ccxt.BaseError: logger.exception(f"Exception in continuously_async_watch_ohlcv for {pair}, {timeframe}") finally: - self._klines_watching.discard((pair, timeframe, candle_type)) + with self._state_lock: + self._klines_watching.discard((pair, timeframe, candle_type)) def schedule_ohlcv(self, pair: str, timeframe: str, candle_type: CandleType) -> None: """ @@ -218,8 +236,9 @@ class ExchangeWS: if not self._wait_for_loop(): logger.warning(f"Websocket loop not ready. Could not schedule {pair}, {timeframe}.") return - self._klines_watching.add((pair, timeframe, candle_type)) - self.klines_last_request[(pair, timeframe, candle_type)] = dt_ts() + with self._state_lock: + self._klines_watching.add((pair, timeframe, candle_type)) + self.klines_last_request[(pair, timeframe, candle_type)] = dt_ts() # asyncio.run_coroutine_threadsafe(self.schedule_schedule(), loop=self._loop) asyncio.run_coroutine_threadsafe(self._schedule_while_true(), loop=self._loop) self.cleanup_expired() @@ -237,10 +256,11 @@ class ExchangeWS: """ # Deepcopy the response - as it might be modified in the background as new messages arrive candles = self.ohlcvs(pair, timeframe) - refresh_date = self.klines_last_refresh[(pair, timeframe, candle_type)] + with self._state_lock: + refresh_date = self.klines_last_refresh.get((pair, timeframe, candle_type), 0) received_ts = candles[-1][0] if candles else 0 drop_hint = received_ts >= candle_ts - if received_ts > refresh_date: + if refresh_date and received_ts > refresh_date: logger.warning( f"{pair}, {timeframe} - Candle date > last refresh " f"({format_ms_time(received_ts)} > {format_ms_time_det(refresh_date)}). " From 057c51b0cdb6a68021511a53628ac5bafad3a582 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Apr 2026 08:47:28 +0200 Subject: [PATCH 290/315] test: improve tests --- tests/exchange/test_exchange_ws.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/exchange/test_exchange_ws.py b/tests/exchange/test_exchange_ws.py index 5071d52af..827102442 100644 --- a/tests/exchange/test_exchange_ws.py +++ b/tests/exchange/test_exchange_ws.py @@ -308,6 +308,33 @@ async def test_exchangews_get_ohlcv(mocker, caplog): exchange_ws.cleanup() +async def test_exchangews_get_ohlcv_missing_refresh_date(mocker, caplog): + config = MagicMock() + ccxt_object = MagicMock() + ccxt_object.ohlcvs = { + "ETH/USDT": { + "1m": [ + [1635840000000, 100, 200, 300, 400, 500], + [1635840060000, 101, 201, 301, 401, 501], + [1635840120000, 102, 202, 302, 402, 502], + ] + } + } + mocker.patch("freqtrade.exchange.exchange_ws.ExchangeWS._start_forever", MagicMock()) + + exchange_ws = ExchangeWS(config, ccxt_object) + exchange_ws.klines_last_refresh = {} + + # No refresh-date entry should not raise KeyError. + resp = await exchange_ws.get_ohlcv("ETH/USDT", "1m", CandleType.SPOT, 1635840120000) + assert resp[0] == "ETH/USDT" + assert resp[1] == "1m" + assert resp[4] is True + assert not log_has_re(r".*Candle date > last refresh.*", caplog) + + exchange_ws.cleanup() + + def test_exchangews_continuous_stopped_task_exception(mocker, caplog): config = MagicMock() ccxt_object = MagicMock() From 17678f1819e157cead0b3522545db2168ae026d8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Apr 2026 08:48:46 +0200 Subject: [PATCH 291/315] refactor: make klines_last_request private --- freqtrade/exchange/exchange_ws.py | 6 +++--- tests/exchange/test_exchange_ws.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/exchange/exchange_ws.py b/freqtrade/exchange/exchange_ws.py index 147541895..9da963184 100644 --- a/freqtrade/exchange/exchange_ws.py +++ b/freqtrade/exchange/exchange_ws.py @@ -29,7 +29,7 @@ class ExchangeWS: self._klines_watching: set[PairWithTimeframe] = set() self._klines_scheduled: set[PairWithTimeframe] = set() self.klines_last_refresh: dict[PairWithTimeframe, float] = {} - self.klines_last_request: dict[PairWithTimeframe, float] = {} + self._klines_last_request: dict[PairWithTimeframe, float] = {} self._thread = Thread(name="ccxt_ws", target=self._start_forever) self._thread.start() @@ -130,7 +130,7 @@ class ExchangeWS: for p in list(self._klines_watching): _, timeframe, _ = p timeframe_s = timeframe_to_seconds(timeframe) - last_refresh = self.klines_last_request.get(p, 0) + last_refresh = self._klines_last_request.get(p, 0) if last_refresh > 0 and (dt_ts() - last_refresh) > ((timeframe_s + 20) * 1000): logger.info(f"Removing {p} from websocket watchlist.") self._klines_watching.discard(p) @@ -238,7 +238,7 @@ class ExchangeWS: return with self._state_lock: self._klines_watching.add((pair, timeframe, candle_type)) - self.klines_last_request[(pair, timeframe, candle_type)] = dt_ts() + self._klines_last_request[(pair, timeframe, candle_type)] = dt_ts() # asyncio.run_coroutine_threadsafe(self.schedule_schedule(), loop=self._loop) asyncio.run_coroutine_threadsafe(self._schedule_while_true(), loop=self._loop) self.cleanup_expired() diff --git a/tests/exchange/test_exchange_ws.py b/tests/exchange/test_exchange_ws.py index 827102442..90987fc8a 100644 --- a/tests/exchange/test_exchange_ws.py +++ b/tests/exchange/test_exchange_ws.py @@ -27,7 +27,7 @@ def test_exchangews_init(mocker): assert exchange_ws._klines_watching == set() assert exchange_ws._klines_scheduled == set() assert exchange_ws.klines_last_refresh == {} - assert exchange_ws.klines_last_request == {} + assert exchange_ws._klines_last_request == {} # Cleanup exchange_ws.cleanup() @@ -120,7 +120,7 @@ def test_exchangews_schedule_ohlcv_loop_not_ready(mocker, caplog): exchange_ws.schedule_ohlcv("ETH/BTC", "1m", CandleType.SPOT) assert exchange_ws._klines_watching == set() - assert exchange_ws.klines_last_request == {} + assert exchange_ws._klines_last_request == {} assert run_threadsafe.call_count == 0 assert log_has_re("Websocket loop not ready. Could not schedule ETH/BTC, 1m", caplog) From c34cd6a7dd6a9a0ef20fff1fc8b1632bcf567e69 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Apr 2026 12:48:56 +0200 Subject: [PATCH 292/315] refactor: don't use exchange_ws internal variables --- freqtrade/exchange/exchange.py | 8 ++++---- freqtrade/exchange/exchange_ws.py | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 61acdf510..81510d047 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -2669,11 +2669,11 @@ class Exchange: if self._can_use_websocket(self._exchange_ws, pair, timeframe, candle_type): candle_ts = dt_ts(timeframe_to_prev_date(timeframe)) prev_candle_ts = dt_ts(date_minus_candles(timeframe, 1)) - candles = self._exchange_ws.ohlcvs(pair, timeframe) - half_candle = int(candle_ts - (candle_ts - prev_candle_ts) * 0.5) - last_refresh_time = int( - self._exchange_ws.klines_last_refresh.get((pair, timeframe, candle_type), 0) + candles, last_refresh_time = self._exchange_ws.get_ohlcv_with_refresh( + pair, timeframe, candle_type ) + last_refresh_time = int(last_refresh_time) + half_candle = int(candle_ts - (candle_ts - prev_candle_ts) * 0.5) if ( candles diff --git a/freqtrade/exchange/exchange_ws.py b/freqtrade/exchange/exchange_ws.py index 9da963184..442932ba2 100644 --- a/freqtrade/exchange/exchange_ws.py +++ b/freqtrade/exchange/exchange_ws.py @@ -120,6 +120,17 @@ class ExchangeWS: # TemporaryError does not cause backoff - so we're essentially retrying immediately raise TemporaryError(f"Error deepcopying: {e}") from e + def get_ohlcv_with_refresh( + self, pair: str, timeframe: str, candle_type: CandleType + ) -> tuple[list[list], float]: + """ + Get deepcopied klines and update the last refresh time + """ + ohlcvs = self.ohlcvs(pair, timeframe) + with self._state_lock: + last_refresh = self.klines_last_refresh.get((pair, timeframe, candle_type), 0) + return ohlcvs, last_refresh + def cleanup_expired(self) -> None: """ Remove pairs from watchlist if they've not been requested within @@ -254,10 +265,7 @@ class ExchangeWS: Returns cached klines from ccxt's "watch" cache. :param candle_ts: timestamp of the end-time of the candle we expect. """ - # Deepcopy the response - as it might be modified in the background as new messages arrive - candles = self.ohlcvs(pair, timeframe) - with self._state_lock: - refresh_date = self.klines_last_refresh.get((pair, timeframe, candle_type), 0) + candles, refresh_date = self.get_ohlcv_with_refresh(pair, timeframe, candle_type) received_ts = candles[-1][0] if candles else 0 drop_hint = received_ts >= candle_ts if refresh_date and received_ts > refresh_date: From 093cc74774045fd5885e2a10e82303d8e5dbe14e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Apr 2026 12:49:28 +0200 Subject: [PATCH 293/315] refactor: make klines_last_refresh private --- freqtrade/exchange/exchange_ws.py | 8 ++++---- tests/exchange/test_exchange_ws.py | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/freqtrade/exchange/exchange_ws.py b/freqtrade/exchange/exchange_ws.py index 442932ba2..38cc0a2aa 100644 --- a/freqtrade/exchange/exchange_ws.py +++ b/freqtrade/exchange/exchange_ws.py @@ -28,7 +28,7 @@ class ExchangeWS: self._klines_watching: set[PairWithTimeframe] = set() self._klines_scheduled: set[PairWithTimeframe] = set() - self.klines_last_refresh: dict[PairWithTimeframe, float] = {} + self._klines_last_refresh: dict[PairWithTimeframe, float] = {} self._klines_last_request: dict[PairWithTimeframe, float] = {} self._thread = Thread(name="ccxt_ws", target=self._start_forever) self._thread.start() @@ -104,7 +104,7 @@ class ExchangeWS: """ with self._state_lock: self._ccxt_object.ohlcvs.get(paircomb[0], {}).pop(paircomb[1], None) - self.klines_last_refresh.pop(paircomb, None) + self._klines_last_refresh.pop(paircomb, None) @retrier(retries=3) def ohlcvs(self, pair: str, timeframe: str) -> list[list]: @@ -128,7 +128,7 @@ class ExchangeWS: """ ohlcvs = self.ohlcvs(pair, timeframe) with self._state_lock: - last_refresh = self.klines_last_refresh.get((pair, timeframe, candle_type), 0) + last_refresh = self._klines_last_refresh.get((pair, timeframe, candle_type), 0) return ohlcvs, last_refresh def cleanup_expired(self) -> None: @@ -227,7 +227,7 @@ class ExchangeWS: start = dt_ts() data = await self._ccxt_object.watch_ohlcv(pair, timeframe) with self._state_lock: - self.klines_last_refresh[(pair, timeframe, candle_type)] = dt_ts() + self._klines_last_refresh[(pair, timeframe, candle_type)] = dt_ts() logger.debug( f"watch done {pair}, {timeframe}, data {len(data)} " f"in {(dt_ts() - start) / 1000:.3f}s" diff --git a/tests/exchange/test_exchange_ws.py b/tests/exchange/test_exchange_ws.py index 90987fc8a..a9a247b20 100644 --- a/tests/exchange/test_exchange_ws.py +++ b/tests/exchange/test_exchange_ws.py @@ -26,7 +26,7 @@ def test_exchangews_init(mocker): assert exchange_ws._background_tasks == set() assert exchange_ws._klines_watching == set() assert exchange_ws._klines_scheduled == set() - assert exchange_ws.klines_last_refresh == {} + assert exchange_ws._klines_last_refresh == {} assert exchange_ws._klines_last_request == {} # Cleanup exchange_ws.cleanup() @@ -258,7 +258,7 @@ async def test_exchangews_get_ohlcv(mocker, caplog): mocker.patch("freqtrade.exchange.exchange_ws.ExchangeWS._start_forever", MagicMock()) exchange_ws = ExchangeWS(config, ccxt_object) - exchange_ws.klines_last_refresh = { + exchange_ws._klines_last_refresh = { ("ETH/USDT", "1m", CandleType.SPOT): 1635840120000, ("ETH/USDT", "5m", CandleType.SPOT): 1635840600000, } @@ -287,7 +287,7 @@ async def test_exchangews_get_ohlcv(mocker, caplog): # Change "received" times to be before the candle starts. # This should trigger the "time sync" warning. - exchange_ws.klines_last_refresh = { + exchange_ws._klines_last_refresh = { ("ETH/USDT", "1m", CandleType.SPOT): 1635840110000, ("ETH/USDT", "5m", CandleType.SPOT): 1635840600000, } @@ -323,7 +323,7 @@ async def test_exchangews_get_ohlcv_missing_refresh_date(mocker, caplog): mocker.patch("freqtrade.exchange.exchange_ws.ExchangeWS._start_forever", MagicMock()) exchange_ws = ExchangeWS(config, ccxt_object) - exchange_ws.klines_last_refresh = {} + exchange_ws._klines_last_refresh = {} # No refresh-date entry should not raise KeyError. resp = await exchange_ws.get_ohlcv("ETH/USDT", "1m", CandleType.SPOT, 1635840120000) @@ -355,7 +355,7 @@ def test_exchangews_continuous_stopped_task_exception(mocker, caplog): paircomb = ("ETH/USDT", "1m", CandleType.SPOT) exchange_ws._klines_scheduled.add(paircomb) - exchange_ws.klines_last_refresh[paircomb] = 1 + exchange_ws._klines_last_refresh[paircomb] = 1 task = MagicMock() task.cancelled.return_value = False @@ -378,7 +378,7 @@ def test_exchangews_continuous_stopped_task_exception(mocker, caplog): assert task not in exchange_ws._background_tasks assert paircomb not in exchange_ws._klines_scheduled - assert paircomb not in exchange_ws.klines_last_refresh + assert paircomb not in exchange_ws._klines_last_refresh assert ccxt_object.ohlcvs["ETH/USDT"].get("1m") is None assert run_threadsafe.call_count == 1 assert log_has_re("Unhandled exception in watch task callback for ETH/USDT, 1m", caplog) From f17e383157111d03740cc737622a8766d2f1265f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 25 Apr 2026 13:09:34 +0200 Subject: [PATCH 294/315] test: add tests for get_ohlcv and ohlcvs --- tests/exchange/test_exchange_ws.py | 64 ++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/exchange/test_exchange_ws.py b/tests/exchange/test_exchange_ws.py index a9a247b20..ef431d9a4 100644 --- a/tests/exchange/test_exchange_ws.py +++ b/tests/exchange/test_exchange_ws.py @@ -5,9 +5,11 @@ from datetime import timedelta from time import sleep from unittest.mock import AsyncMock, MagicMock +import pytest from ccxt import NotSupported from freqtrade.enums import CandleType +from freqtrade.exceptions import TemporaryError from freqtrade.exchange.exchange_ws import ExchangeWS from ft_client.test_client.test_rest_client import log_has_re @@ -335,6 +337,68 @@ async def test_exchangews_get_ohlcv_missing_refresh_date(mocker, caplog): exchange_ws.cleanup() +def test_exchangews_ohlcvs_deepcopy_and_retry(mocker): + config = MagicMock() + ccxt_object = MagicMock() + ccxt_object.ohlcvs = { + "ETH/USDT": { + "1m": [[1, 2, 3, 4, 5, 6]], + } + } + mocker.patch("freqtrade.exchange.exchange_ws.ExchangeWS._start_forever", MagicMock()) + + exchange_ws = ExchangeWS(config, ccxt_object) + + call_count = {"count": 0} + + def deepcopy_side_effect(value): + call_count["count"] += 1 + if call_count["count"] < 3: + raise RuntimeError("copy failed") + return [candle.copy() for candle in value] + + mocker.patch("freqtrade.exchange.exchange_ws.deepcopy", deepcopy_side_effect) + + result = exchange_ws.ohlcvs("ETH/USDT", "1m") + + assert call_count["count"] == 3 + assert result == [[1, 2, 3, 4, 5, 6]] + assert result is not ccxt_object.ohlcvs["ETH/USDT"]["1m"] + + # Fail all the time + mocker.patch("freqtrade.exchange.exchange_ws.deepcopy", side_effect=RuntimeError("copy failed")) + with pytest.raises(TemporaryError, match=r"Error deepcopying: copy failed"): + exchange_ws.ohlcvs("ETH/USDT", "1m") + + exchange_ws.cleanup() + + +def test_exchangews_get_ohlcv_with_refresh(mocker): + config = MagicMock() + ccxt_object = MagicMock() + mocker.patch("freqtrade.exchange.exchange_ws.ExchangeWS._start_forever", MagicMock()) + + exchange_ws = ExchangeWS(config, ccxt_object) + ohlcvs_mock = mocker.patch.object( + exchange_ws, "ohlcvs", return_value=[[10, 11, 12, 13, 14, 15]] + ) + + paircomb = ("ETH/USDT", "1m", CandleType.SPOT) + exchange_ws._klines_last_refresh[paircomb] = 123456789 + + candles, refresh = exchange_ws.get_ohlcv_with_refresh("ETH/USDT", "1m", CandleType.SPOT) + + ohlcvs_mock.assert_called_once_with("ETH/USDT", "1m") + assert candles == [[10, 11, 12, 13, 14, 15]] + assert refresh == 123456789 + + candles, refresh = exchange_ws.get_ohlcv_with_refresh("ETH/USDT", "5m", CandleType.SPOT) + assert candles == [[10, 11, 12, 13, 14, 15]] + assert refresh == 0 + + exchange_ws.cleanup() + + def test_exchangews_continuous_stopped_task_exception(mocker, caplog): config = MagicMock() ccxt_object = MagicMock() From e5f7d0cbb43352eb66c30aaa7004e7c901062263 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 26 Apr 2026 09:05:58 +0200 Subject: [PATCH 295/315] fix: bitget should set margin mode explicitly Without this, leverage may be set on the wrong margin mode. the leverage endpoint doesn't allow setting margin mode, so we must control this manually. --- freqtrade/exchange/bitget.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/freqtrade/exchange/bitget.py b/freqtrade/exchange/bitget.py index 1119a6455..fa9cae3f4 100644 --- a/freqtrade/exchange/bitget.py +++ b/freqtrade/exchange/bitget.py @@ -177,12 +177,6 @@ class Bitget(Exchange): except ccxt.BaseError as e: raise OperationalException(e) from e - def _lev_prep(self, pair: str, leverage: float, side: BuySell, accept_fail: bool = False): - if self.trading_mode != TradingMode.SPOT: - # Explicitly setting margin_mode is not necessary as marginMode can be set per order. - # self.set_margin_mode(pair, self.margin_mode, accept_fail) - self._set_leverage(leverage, pair, accept_fail) - def _get_params( self, side: BuySell, From 0a49903695b9cf56f1aff3ade386588bdc6a6883 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 26 Apr 2026 09:07:02 +0200 Subject: [PATCH 296/315] test: update bitget test for lev_prep adjustment --- tests/exchange/test_bitget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/exchange/test_bitget.py b/tests/exchange/test_bitget.py index 575979aca..28059ead8 100644 --- a/tests/exchange/test_bitget.py +++ b/tests/exchange/test_bitget.py @@ -218,7 +218,7 @@ def test__lev_prep_bitget(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, exchange="bitget") exchange._lev_prep("BTC/USDC:USDC", 3.2, "buy") - assert api_mock.set_margin_mode.call_count == 0 + assert api_mock.set_margin_mode.call_count == 1 assert api_mock.set_leverage.call_count == 1 api_mock.set_leverage.assert_called_with(symbol="BTC/USDC:USDC", leverage=3.2) @@ -226,7 +226,7 @@ def test__lev_prep_bitget(default_conf, mocker): exchange._lev_prep("BTC/USDC:USDC", 19.99, "sell") - assert api_mock.set_margin_mode.call_count == 0 + assert api_mock.set_margin_mode.call_count == 1 assert api_mock.set_leverage.call_count == 1 api_mock.set_leverage.assert_called_with(symbol="BTC/USDC:USDC", leverage=19.99) From 159bde944b7e8bbc98f424f972e3195d16a466e2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 03:03:14 +0000 Subject: [PATCH 297/315] chore(deps-dev): bump scipy-stubs in the scipy group Bumps the scipy group with 1 update: [scipy-stubs](https://github.com/scipy/scipy-stubs). Updates `scipy-stubs` from 1.17.1.3 to 1.17.1.4 - [Release notes](https://github.com/scipy/scipy-stubs/releases) - [Commits](https://github.com/scipy/scipy-stubs/compare/v1.17.1.3...v1.17.1.4) --- updated-dependencies: - dependency-name: scipy-stubs dependency-version: 1.17.1.4 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: scipy ... 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 c231373f9..6de275f43 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -23,7 +23,7 @@ time-machine==3.2.0 nbconvert==7.17.1 # mypy types -scipy-stubs==1.17.1.3 # keep in sync with `scipy` in `requirements-hyperopt.txt` +scipy-stubs==1.17.1.4 # keep in sync with `scipy` in `requirements-hyperopt.txt` types-cachetools==6.2.0.20260408 types-filelock==3.2.7 types-requests==2.33.0.20260408 From 14ee96172affbf37d7271ccb1c9258f3533bf0f1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 03:03:23 +0000 Subject: [PATCH 298/315] chore(deps): update sb3-contrib requirement from >=2.2.1 to >=2.8.0 Updates the requirements on [sb3-contrib](https://github.com/Stable-Baselines-Team/stable-baselines3-contrib) to permit the latest version. - [Release notes](https://github.com/Stable-Baselines-Team/stable-baselines3-contrib/releases) - [Commits](https://github.com/Stable-Baselines-Team/stable-baselines3-contrib/compare/v2.2.1...v2.8.0) --- updated-dependencies: - dependency-name: sb3-contrib dependency-version: 2.8.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements-freqai-rl.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai-rl.txt b/requirements-freqai-rl.txt index 61d34f386..707612f16 100644 --- a/requirements-freqai-rl.txt +++ b/requirements-freqai-rl.txt @@ -6,6 +6,6 @@ torch==2.11.0; sys_platform != 'darwin' or platform_machine != 'x86_64' gymnasium==1.2.3 # SB3 >=2.5.0 depends on torch 2.3.0 - which implies it dropped support x86 macos stable_baselines3==2.8.0; sys_platform != 'darwin' or platform_machine != 'x86_64' -sb3_contrib>=2.2.1; sys_platform != 'darwin' or platform_machine != 'x86_64' +sb3_contrib>=2.8.0; sys_platform != 'darwin' or platform_machine != 'x86_64' # Progress bar for stable-baselines3 and sb3-contrib tqdm==4.67.3 From 1f941542202544e751365af659a8d1fe6170eecd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 03:03:37 +0000 Subject: [PATCH 299/315] chore(deps): bump fastapi from 0.135.3 to 0.136.0 Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.135.3 to 0.136.0. - [Release notes](https://github.com/fastapi/fastapi/releases) - [Commits](https://github.com/fastapi/fastapi/compare/0.135.3...0.136.0) --- updated-dependencies: - dependency-name: fastapi dependency-version: 0.136.0 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 f3143e35a..6b4e490ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,7 +37,7 @@ orjson==3.11.8 sdnotify==0.3.2 # API Server -fastapi==0.135.3 +fastapi==0.136.0 pydantic==2.12.5 uvicorn==0.44.0 pyjwt==2.12.1 From 700d4ea2fff6cb0847f489f588982dbcda9e3c4a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 03:03:47 +0000 Subject: [PATCH 300/315] chore(deps): bump packaging from 26.0 to 26.1 Bumps [packaging](https://github.com/pypa/packaging) from 26.0 to 26.1. - [Release notes](https://github.com/pypa/packaging/releases) - [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pypa/packaging/compare/26.0...26.1) --- updated-dependencies: - dependency-name: packaging dependency-version: '26.1' 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 f3143e35a..029cd9274 100644 --- a/requirements.txt +++ b/requirements.txt @@ -59,4 +59,4 @@ websockets==16.0 janus==2.0.0 ast-comments==1.3.0 -packaging==26.0 +packaging==26.1 From e2879f11f5c082119401655be5edf8f00e20aa07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 03:03:57 +0000 Subject: [PATCH 301/315] chore(deps): bump astral-sh/setup-uv from 8.0.0 to 8.1.0 Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 8.0.0 to 8.1.0. - [Release notes](https://github.com/astral-sh/setup-uv/releases) - [Commits](https://github.com/astral-sh/setup-uv/compare/cec208311dfd045dd5311c1add060b2062131d57...08807647e7069bb48b6ef5acd8ec9567f424441b) --- updated-dependencies: - dependency-name: astral-sh/setup-uv dependency-version: 8.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/binance-lev-tier-update.yml | 2 +- .github/workflows/ci.yml | 10 +++++----- .github/workflows/deploy-docs.yml | 2 +- .github/workflows/pre-commit-update.yml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/binance-lev-tier-update.yml b/.github/workflows/binance-lev-tier-update.yml index 628fca8f5..e56cc9a4b 100644 --- a/.github/workflows/binance-lev-tier-update.yml +++ b/.github/workflows/binance-lev-tier-update.yml @@ -25,7 +25,7 @@ jobs: persist-credentials: false - name: Install uv and Python 🐍 - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: activate-environment: true enable-cache: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce14e4861..8bb925792 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: persist-credentials: false - name: Install uv and Python 🐍 - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: activate-environment: true enable-cache: true @@ -173,7 +173,7 @@ jobs: persist-credentials: false - name: Install uv and Python 🐍 - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: activate-environment: true python-version: "3.13" @@ -211,7 +211,7 @@ jobs: ./tests/test_docs.sh - name: Install uv and Python 🐍 - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: activate-environment: true python-version: "3.13" @@ -243,7 +243,7 @@ jobs: persist-credentials: false - name: Install uv and Python 🐍 - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: activate-environment: true enable-cache: true @@ -310,7 +310,7 @@ jobs: persist-credentials: false - name: Install uv and Python 🐍 - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: activate-environment: true python-version: "${{ matrix.python-version }}" diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml index e30a0edae..741799baa 100644 --- a/.github/workflows/deploy-docs.yml +++ b/.github/workflows/deploy-docs.yml @@ -27,7 +27,7 @@ jobs: persist-credentials: true - name: Install uv and Python 🐍 - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: activate-environment: true python-version: '3.13' diff --git a/.github/workflows/pre-commit-update.yml b/.github/workflows/pre-commit-update.yml index 6a918bc61..5bea4c0e6 100644 --- a/.github/workflows/pre-commit-update.yml +++ b/.github/workflows/pre-commit-update.yml @@ -26,7 +26,7 @@ jobs: persist-credentials: false - name: Install uv and Python 🐍 - uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0 + uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0 with: activate-environment: true python-version: "3.13" From 668f7b7e9cdada15fc94a85b23b617259d296455 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 03:04:06 +0000 Subject: [PATCH 302/315] chore(deps): bump devcontainers/ci from 0.3.1900000417 to 0.3.1900000449 Bumps [devcontainers/ci](https://github.com/devcontainers/ci) from 0.3.1900000417 to 0.3.1900000449. - [Release notes](https://github.com/devcontainers/ci/releases) - [Commits](https://github.com/devcontainers/ci/compare/8bf61b26e9c3a98f69cb6ce2f88d24ff59b785c6...b63b30de439b47a52267f241112c5b453b673db5) --- updated-dependencies: - dependency-name: devcontainers/ci dependency-version: 0.3.1900000449 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/devcontainer-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/devcontainer-build.yml b/.github/workflows/devcontainer-build.yml index c3a08c043..05d152f00 100644 --- a/.github/workflows/devcontainer-build.yml +++ b/.github/workflows/devcontainer-build.yml @@ -37,7 +37,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Pre-build dev container image - uses: devcontainers/ci@8bf61b26e9c3a98f69cb6ce2f88d24ff59b785c6 # v0.3.1900000417 + uses: devcontainers/ci@b63b30de439b47a52267f241112c5b453b673db5 # v0.3.1900000449 with: subFolder: .github imageName: ghcr.io/${{ github.repository }}-devcontainer From cf5ba35bb0378ef813643fab96a576ee7d8b417a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 03:04:07 +0000 Subject: [PATCH 303/315] chore(deps-dev): bump ruff from 0.15.10 to 0.15.11 Bumps [ruff](https://github.com/astral-sh/ruff) from 0.15.10 to 0.15.11. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.15.10...0.15.11) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.15.11 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 c231373f9..4f797467f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ -r requirements-freqai-rl.txt -r docs/requirements-docs.txt -ruff==0.15.10 +ruff==0.15.11 mypy==1.20.1 pre-commit==4.5.1 pytest==9.0.3 From a5cb6073110eb024c31da3fefbb3602fde9414cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 03:04:14 +0000 Subject: [PATCH 304/315] chore(deps): bump mike from 2.1.4 to 2.2.0 Bumps [mike](https://github.com/jimporter/mike) from 2.1.4 to 2.2.0. - [Release notes](https://github.com/jimporter/mike/releases) - [Changelog](https://github.com/jimporter/mike/blob/master/CHANGES.md) - [Commits](https://github.com/jimporter/mike/compare/v2.1.4...v2.2.0) --- updated-dependencies: - dependency-name: mike dependency-version: 2.2.0 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 28b850b19..fbdb3701e 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -4,4 +4,4 @@ mkdocs-material==9.7.6 mdx_truly_sane_lists==1.3 pymdown-extensions==10.21.2 jinja2==3.1.6 -mike==2.1.4 +mike==2.2.0 From 5a8131caf6dd67730074854517178133722cb344 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 03:04:21 +0000 Subject: [PATCH 305/315] chore(deps): bump filelock from 3.25.2 to 3.29.0 Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.25.2 to 3.29.0. - [Release notes](https://github.com/tox-dev/py-filelock/releases) - [Changelog](https://github.com/tox-dev/filelock/blob/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/py-filelock/compare/3.25.2...3.29.0) --- updated-dependencies: - dependency-name: filelock dependency-version: 3.29.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 32ef55806..013c18ee4 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -4,6 +4,6 @@ # Required for hyperopt scipy==1.17.1 scikit-learn==1.8.0 -filelock==3.25.2 +filelock==3.29.0 optuna==4.8.0 cmaes==0.13.0 From 875c464a07870c9718cdfa4582e2167c1d355807 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 27 Apr 2026 06:32:00 +0200 Subject: [PATCH 306/315] chore: bump scipy-stubs in pre-commit config --- .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 08728523b..da52a16f7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: - types-requests==2.33.0.20260408 - types-tabulate==0.10.0.20260408 - types-python-dateutil==2.9.0.20260408 - - scipy-stubs==1.17.1.3 + - scipy-stubs==1.17.1.4 - SQLAlchemy==2.0.49 # stages: [push] From bab128b3595a1ea5bd2295f89c45f100cecfc9a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Apr 2026 04:32:29 +0000 Subject: [PATCH 307/315] chore(deps): bump pydantic from 2.12.5 to 2.13.2 Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.12.5 to 2.13.2. - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md) - [Commits](https://github.com/pydantic/pydantic/compare/v2.12.5...v2.13.2) --- updated-dependencies: - dependency-name: pydantic dependency-version: 2.13.2 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 6b4e490ea..bf49290b8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -38,7 +38,7 @@ sdnotify==0.3.2 # API Server fastapi==0.136.0 -pydantic==2.12.5 +pydantic==2.13.2 uvicorn==0.44.0 pyjwt==2.12.1 aiofiles==25.1.0 From 5b83dce052186b2cfe3a399618192c329e002818 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 27 Apr 2026 06:37:09 +0200 Subject: [PATCH 308/315] chore: precisely pin sb3_contrib --- requirements-freqai-rl.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-freqai-rl.txt b/requirements-freqai-rl.txt index 707612f16..2056dac30 100644 --- a/requirements-freqai-rl.txt +++ b/requirements-freqai-rl.txt @@ -6,6 +6,6 @@ torch==2.11.0; sys_platform != 'darwin' or platform_machine != 'x86_64' gymnasium==1.2.3 # SB3 >=2.5.0 depends on torch 2.3.0 - which implies it dropped support x86 macos stable_baselines3==2.8.0; sys_platform != 'darwin' or platform_machine != 'x86_64' -sb3_contrib>=2.8.0; sys_platform != 'darwin' or platform_machine != 'x86_64' +sb3_contrib==2.8.0; sys_platform != 'darwin' or platform_machine != 'x86_64' # Progress bar for stable-baselines3 and sb3-contrib tqdm==4.67.3 From 820000fac42af4cf33b279eedfa8d04b6f932304 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 27 Apr 2026 07:16:55 +0200 Subject: [PATCH 309/315] chore: update dry-run order-id generation to uuid this is supposed to avoid insert errors in windows CI due to time imprecisions on windows ... Co-authored-by: Copilot --- freqtrade/exchange/exchange.py | 3 ++- tests/exchange/test_exchange.py | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 81510d047..9531014c8 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -13,6 +13,7 @@ from datetime import UTC, datetime, timedelta from math import floor, isnan from threading import Lock from typing import Any, Literal, TypeGuard, TypeVar +from uuid import uuid4 import ccxt import ccxt.pro as ccxt_pro @@ -1152,7 +1153,7 @@ class Exchange: stop_price: float | None = None, ) -> CcxtOrder: now = dt_now() - order_id = f"dry_run_{side}_{pair}_{now.timestamp()}" + order_id = f"dry_run_{side}_{pair}_{uuid4()}" # Rounding here must respect to contract sizes _amount = self._contracts_to_amount( pair, self.amount_to_precision(pair, self._amount_to_contracts(pair, amount)) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 022c410f0..187a5f64e 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1,5 +1,6 @@ import copy import logging +import re from copy import deepcopy from datetime import UTC, datetime, timedelta from random import randint @@ -1077,6 +1078,24 @@ def test_create_dry_run_order(default_conf, mocker, side, exchange_name, leverag assert order["cost"] == 1 * 200 +def test_create_dry_run_order_id_unique_with_same_timestamp(default_conf, mocker, time_machine): + exchange = get_patched_exchange(mocker, default_conf) + + time_machine.move_to("2026-04-27T04:49:57.438232Z", tick=False) + order1 = exchange.create_dry_run_order( + pair="ETH/USDT", ordertype="limit", side="sell", amount=1, rate=2.05, leverage=1.0 + ) + order2 = exchange.create_dry_run_order( + pair="ETH/USDT", ordertype="limit", side="sell", amount=1, rate=2.05, leverage=1.0 + ) + + assert order1["id"] != order2["id"] + assert re.match( + r"^dry_run_sell_ETH/USDT_[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$", + order1["id"], + ) + + @pytest.mark.parametrize( "side,is_short,order_reason", [ From afc00c5a2e88553d75b30b0a46481b055cfce4d0 Mon Sep 17 00:00:00 2001 From: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com> Date: Tue, 28 Apr 2026 04:15:25 +0000 Subject: [PATCH 310/315] chore: update pre-commit hooks --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index da52a16f7..dbd3be2c3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ repos: - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.20.1" + rev: "v1.20.2" hooks: - id: mypy exclude: build_helpers @@ -31,7 +31,7 @@ repos: - repo: https://github.com/charliermarsh/ruff-pre-commit # Ruff version. - rev: 'v0.15.11' + rev: 'v0.15.12' hooks: - id: ruff - id: ruff-format From bfe05868eb6e1bf43b144725c10d522904b3c27f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Apr 2026 20:51:47 +0200 Subject: [PATCH 311/315] feat: implement unwatchOHLCV closes #13082 Co-authored-by: Copilot --- freqtrade/exchange/exchange_ws.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/exchange_ws.py b/freqtrade/exchange/exchange_ws.py index 38cc0a2aa..7cffa3f57 100644 --- a/freqtrade/exchange/exchange_ws.py +++ b/freqtrade/exchange/exchange_ws.py @@ -177,9 +177,24 @@ class ExchangeWS: ) ) + def exchange_has(self, endpoint: str) -> bool: + """ + Checks if exchange implements a specific API endpoint. + Wrapper around ccxt 'has' attribute + :param endpoint: Name of endpoint (e.g. 'fetchOHLCV', 'fetchTickers') + :return: bool + """ + return endpoint in self._ccxt_object.has and self._ccxt_object.has[endpoint] + async def _unwatch_ohlcv(self, pair: str, timeframe: str, candle_type: CandleType) -> None: try: - await self._ccxt_object.un_watch_ohlcv_for_symbols([[pair, timeframe]]) + if self.exchange_has("unWatchOHLCVForSymbols"): + await self._ccxt_object.un_watch_ohlcv_for_symbols([[pair, timeframe]]) + elif self.exchange_has("unWatchOHLCV"): + await self._ccxt_object.un_watch_ohlcv(pair, timeframe) + else: + logger.debug("un_watch_ohlcv not supported for %s, %s", pair, timeframe) + except ccxt.NotSupported as e: logger.debug("un_watch_ohlcv_for_symbols not supported: %s", e) pass From 63819fa7e045c313dfcb14a2a22b524cd02cef7d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 28 Apr 2026 20:54:04 +0200 Subject: [PATCH 312/315] test: update test for unwatch_ohlcv adjustment Co-authored-by: Copilot --- tests/exchange/test_exchange_ws.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/exchange/test_exchange_ws.py b/tests/exchange/test_exchange_ws.py index ef431d9a4..315f3ef5f 100644 --- a/tests/exchange/test_exchange_ws.py +++ b/tests/exchange/test_exchange_ws.py @@ -167,6 +167,7 @@ async def test_exchangews_ohlcv(mocker, time_machine, caplog): ccxt_object.un_watch_ohlcv_for_symbols = AsyncMock(side_effect=[NotSupported, ValueError]) ccxt_object.watch_ohlcv = AsyncMock(side_effect=controlled_sleeper) + ccxt_object.has = {"unWatchOHLCVForSymbols": True} ccxt_object.close = AsyncMock() time_machine.move_to("2024-11-01 01:00:02 +00:00") From dd970f2be12d4c47b9444d9ce7b869eefbd12869 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 29 Apr 2026 20:01:50 +0200 Subject: [PATCH 313/315] fix: rate can be None in balance migration --- freqtrade/util/migrations/migrate_wallet_history.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/util/migrations/migrate_wallet_history.py b/freqtrade/util/migrations/migrate_wallet_history.py index dbc887c56..d7070ed5f 100644 --- a/freqtrade/util/migrations/migrate_wallet_history.py +++ b/freqtrade/util/migrations/migrate_wallet_history.py @@ -180,7 +180,7 @@ def _create_wallet_history_entries( if is_futures: collateral = row[pair_collateral_idx[pair]] is_short = row[pair_is_short_idx[pair]] - if collateral is not None and not pd.isna(collateral): + if collateral is not None and not pd.isna(collateral) and rate is not None: # Same formula than in rpc's _rpc_balance total_quote = ( (rate * balance - collateral * (leverage - 1)) From ad8dc57e9bd92c753c49b7395d8980a4de674501 Mon Sep 17 00:00:00 2001 From: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com> Date: Thu, 30 Apr 2026 03:37:35 +0000 Subject: [PATCH 314/315] chore: update binance leverage tiers --- .../exchange/binance_leverage_tiers.json | 3197 +++++++---------- 1 file changed, 1250 insertions(+), 1947 deletions(-) diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json index f9fa032b5..1f92dc082 100644 --- a/freqtrade/exchange/binance_leverage_tiers.json +++ b/freqtrade/exchange/binance_leverage_tiers.json @@ -4142,14 +4142,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.025, "cum": 0.0 } }, @@ -4159,15 +4159,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 2, - "initialLeverage": 20, + "initialLeverage": 10, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.025, - "cum": 50.0 + "maintMarginRatio": 0.05, + "cum": 125.0 } }, { @@ -4175,50 +4175,50 @@ "symbol": "ACE/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 3, - "initialLeverage": 10, - "notionalCap": 20000, + "initialLeverage": 5, + "notionalCap": 60000, "notionalFloor": 10000, - "maintMarginRatio": 0.05, - "cum": 300.0 + "maintMarginRatio": 0.1, + "cum": 625.0 } }, { "tier": 4.0, "symbol": "ACE/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 60000.0, + "maxNotional": 70000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 4, - "initialLeverage": 5, - "notionalCap": 50000, - "notionalFloor": 20000, - "maintMarginRatio": 0.1, - "cum": 1300.0 + "initialLeverage": 4, + "notionalCap": 70000, + "notionalFloor": 60000, + "maintMarginRatio": 0.125, + "cum": 2125.0 } }, { "tier": 5.0, "symbol": "ACE/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, + "minNotional": 70000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 5, - "initialLeverage": 4, + "initialLeverage": 3, "notionalCap": 250000, - "notionalFloor": 50000, - "maintMarginRatio": 0.125, - "cum": 2550.0 + "notionalFloor": 70000, + "maintMarginRatio": 0.1667, + "cum": 5044.0 } }, { @@ -4227,15 +4227,15 @@ "currency": "USDT", "minNotional": 250000.0, "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 6, - "initialLeverage": 3, + "initialLeverage": 2, "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1667, - "cum": 12975.0 + "maintMarginRatio": 0.25, + "cum": 25869.0 } }, { @@ -4243,33 +4243,16 @@ "symbol": "ACE/USDT:USDT", "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 7500000, - "notionalFloor": 500000, - "maintMarginRatio": 0.25, - "cum": 54625.0 - } - }, - { - "tier": 8.0, - "symbol": "ACE/USDT:USDT", - "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 12500000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 7, "initialLeverage": 1, - "notionalCap": 12500000, - "notionalFloor": 7500000, + "notionalCap": 800000, + "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 1929625.0 + "cum": 150869.0 } } ], @@ -5986,15 +5969,15 @@ "symbol": "AI/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 1, - "initialLeverage": 25, - "notionalCap": 5000, + "initialLeverage": 10, + "notionalCap": 10000, "notionalFloor": 0, - "maintMarginRatio": 0.02, + "maintMarginRatio": 0.05, "cum": 0.0 } }, @@ -6002,55 +5985,21 @@ "tier": 2.0, "symbol": "AI/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, - "info": { - "bracket": 2, - "initialLeverage": 20, - "notionalCap": 10000, - "notionalFloor": 5000, - "maintMarginRatio": 0.025, - "cum": 25.0 - } - }, - { - "tier": 3.0, - "symbol": "AI/USDT:USDT", - "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": 3, - "initialLeverage": 10, - "notionalCap": 20000, - "notionalFloor": 10000, - "maintMarginRatio": 0.05, - "cum": 275.0 - } - }, - { - "tier": 4.0, - "symbol": "AI/USDT:USDT", - "currency": "USDT", - "minNotional": 20000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": 4, + "bracket": 2, "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 20000, + "notionalFloor": 10000, "maintMarginRatio": 0.1, - "cum": 1275.0 + "cum": 500.0 } }, { - "tier": 5.0, + "tier": 3.0, "symbol": "AI/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, @@ -6058,16 +6007,16 @@ "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 5, + "bracket": 3, "initialLeverage": 4, "notionalCap": 100000, "notionalFloor": 50000, "maintMarginRatio": 0.125, - "cum": 2525.0 + "cum": 1750.0 } }, { - "tier": 6.0, + "tier": 4.0, "symbol": "AI/USDT:USDT", "currency": "USDT", "minNotional": 100000.0, @@ -6075,46 +6024,46 @@ "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 6, + "bracket": 4, "initialLeverage": 3, "notionalCap": 250000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 6695.0 + "cum": 5920.0 } }, { - "tier": 7.0, + "tier": 5.0, "symbol": "AI/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, - "maxNotional": 2500000.0, + "maxNotional": 500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 7, + "bracket": 5, "initialLeverage": 2, - "notionalCap": 2500000, + "notionalCap": 500000, "notionalFloor": 250000, "maintMarginRatio": 0.25, - "cum": 27520.0 + "cum": 26745.0 } }, { - "tier": 8.0, + "tier": 6.0, "symbol": "AI/USDT:USDT", "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 500000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 6, "initialLeverage": 1, - "notionalCap": 5000000, - "notionalFloor": 2500000, + "notionalCap": 800000, + "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 652520.0 + "cum": 151745.0 } } ], @@ -6377,6 +6326,127 @@ } } ], + "AIGENSYN/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "AIGENSYN/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": 1, + "initialLeverage": 20, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.025, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "AIGENSYN/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 2, + "initialLeverage": 10, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.05, + "cum": 125.0 + } + }, + { + "tier": 3.0, + "symbol": "AIGENSYN/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 50000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 3, + "initialLeverage": 5, + "notionalCap": 50000, + "notionalFloor": 10000, + "maintMarginRatio": 0.1, + "cum": 625.0 + } + }, + { + "tier": 4.0, + "symbol": "AIGENSYN/USDT:USDT", + "currency": "USDT", + "minNotional": 50000.0, + "maxNotional": 100000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 4, + "initialLeverage": 4, + "notionalCap": 100000, + "notionalFloor": 50000, + "maintMarginRatio": 0.125, + "cum": 1875.0 + } + }, + { + "tier": 5.0, + "symbol": "AIGENSYN/USDT:USDT", + "currency": "USDT", + "minNotional": 100000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 5, + "initialLeverage": 3, + "notionalCap": 250000, + "notionalFloor": 100000, + "maintMarginRatio": 0.1667, + "cum": 6045.0 + } + }, + { + "tier": 6.0, + "symbol": "AIGENSYN/USDT:USDT", + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 6, + "initialLeverage": 2, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.25, + "cum": 26870.0 + } + }, + { + "tier": 7.0, + "symbol": "AIGENSYN/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 7, + "initialLeverage": 1, + "notionalCap": 800000, + "notionalFloor": 500000, + "maintMarginRatio": 0.5, + "cum": 151870.0 + } + } + ], "AIN/USDT:USDT": [ { "tier": 1.0, @@ -10403,14 +10473,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -10420,15 +10490,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, - "cum": 25.0 + "maintMarginRatio": 0.025, + "cum": 50.0 } }, { @@ -10436,33 +10506,33 @@ "symbol": "ARKM/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 25000, + "initialLeverage": 10, + "notionalCap": 20000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, - "cum": 75.0 + "maintMarginRatio": 0.05, + "cum": 300.0 } }, { "tier": 4.0, "symbol": "ARKM/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, + "minNotional": 20000.0, "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 4, - "initialLeverage": 20, + "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 25000, - "maintMarginRatio": 0.025, - "cum": 200.0 + "notionalFloor": 20000, + "maintMarginRatio": 0.1, + "cum": 1300.0 } }, { @@ -10470,88 +10540,54 @@ "symbol": "ARKM/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 125000, + "initialLeverage": 4, + "notionalCap": 250000, "notionalFloor": 50000, - "maintMarginRatio": 0.05, - "cum": 1450.0 + "maintMarginRatio": 0.125, + "cum": 2550.0 } }, { "tier": 6.0, "symbol": "ARKM/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.1, - "cum": 7700.0 + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 12975.0 } }, { "tier": 7.0, "symbol": "ARKM/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 7, - "initialLeverage": 4, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.125, - "cum": 13950.0 - } - }, - { - "tier": 8.0, - "symbol": "ARKM/USDT:USDT", - "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 8, - "initialLeverage": 3, - "notionalCap": 1000000, - "notionalFloor": 500000, - "maintMarginRatio": 0.1667, - "cum": 34800.0 - } - }, - { - "tier": 9.0, - "symbol": "ARKM/USDT:USDT", - "currency": "USDT", - "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 9, + "bracket": 7, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 1000000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 118100.0 + "cum": 54625.0 } }, { - "tier": 10.0, + "tier": 8.0, "symbol": "ARKM/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -10559,12 +10595,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 1993100.0 + "cum": 1929625.0 } } ], @@ -11747,14 +11783,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -11764,15 +11800,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, - "cum": 25.0 + "maintMarginRatio": 0.025, + "cum": 50.0 } }, { @@ -11780,33 +11816,33 @@ "symbol": "AUCTION/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 25000, + "initialLeverage": 10, + "notionalCap": 20000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, - "cum": 75.0 + "maintMarginRatio": 0.05, + "cum": 300.0 } }, { "tier": 4.0, "symbol": "AUCTION/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, + "minNotional": 20000.0, "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 4, - "initialLeverage": 20, + "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 25000, - "maintMarginRatio": 0.025, - "cum": 200.0 + "notionalFloor": 20000, + "maintMarginRatio": 0.1, + "cum": 1300.0 } }, { @@ -11814,88 +11850,54 @@ "symbol": "AUCTION/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 125000, + "initialLeverage": 4, + "notionalCap": 250000, "notionalFloor": 50000, - "maintMarginRatio": 0.05, - "cum": 1450.0 + "maintMarginRatio": 0.125, + "cum": 2550.0 } }, { "tier": 6.0, "symbol": "AUCTION/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.1, - "cum": 7700.0 + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 12975.0 } }, { "tier": 7.0, "symbol": "AUCTION/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 7, - "initialLeverage": 4, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.125, - "cum": 13950.0 - } - }, - { - "tier": 8.0, - "symbol": "AUCTION/USDT:USDT", - "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 8, - "initialLeverage": 3, - "notionalCap": 1000000, - "notionalFloor": 500000, - "maintMarginRatio": 0.1667, - "cum": 34800.0 - } - }, - { - "tier": 9.0, - "symbol": "AUCTION/USDT:USDT", - "currency": "USDT", - "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 9, + "bracket": 7, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 1000000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 118100.0 + "cum": 54625.0 } }, { - "tier": 10.0, + "tier": 8.0, "symbol": "AUCTION/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -11903,12 +11905,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 1993100.0 + "cum": 1929625.0 } } ], @@ -13710,15 +13712,15 @@ "symbol": "B3/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.04, - "maxLeverage": 20.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 1, - "initialLeverage": 20, - "notionalCap": 5000, + "initialLeverage": 10, + "notionalCap": 10000, "notionalFloor": 0, - "maintMarginRatio": 0.04, + "maintMarginRatio": 0.05, "cum": 0.0 } }, @@ -13726,38 +13728,21 @@ "tier": 2.0, "symbol": "B3/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": 2, - "initialLeverage": 10, - "notionalCap": 10000, - "notionalFloor": 5000, - "maintMarginRatio": 0.05, - "cum": 50.0 - } - }, - { - "tier": 3.0, - "symbol": "B3/USDT:USDT", - "currency": "USDT", "minNotional": 10000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": 3, + "bracket": 2, "initialLeverage": 5, "notionalCap": 50000, "notionalFloor": 10000, "maintMarginRatio": 0.1, - "cum": 550.0 + "cum": 500.0 } }, { - "tier": 4.0, + "tier": 3.0, "symbol": "B3/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, @@ -13765,16 +13750,16 @@ "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 4, + "bracket": 3, "initialLeverage": 4, "notionalCap": 100000, "notionalFloor": 50000, "maintMarginRatio": 0.125, - "cum": 1800.0 + "cum": 1750.0 } }, { - "tier": 5.0, + "tier": 4.0, "symbol": "B3/USDT:USDT", "currency": "USDT", "minNotional": 100000.0, @@ -13782,16 +13767,16 @@ "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 5, + "bracket": 4, "initialLeverage": 3, "notionalCap": 250000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 5970.0 + "cum": 5920.0 } }, { - "tier": 6.0, + "tier": 5.0, "symbol": "B3/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, @@ -13799,16 +13784,16 @@ "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 6, + "bracket": 5, "initialLeverage": 2, "notionalCap": 300000, "notionalFloor": 250000, "maintMarginRatio": 0.25, - "cum": 26795.0 + "cum": 26745.0 } }, { - "tier": 7.0, + "tier": 6.0, "symbol": "B3/USDT:USDT", "currency": "USDT", "minNotional": 300000.0, @@ -13816,12 +13801,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 7, + "bracket": 6, "initialLeverage": 1, "notionalCap": 500000, "notionalFloor": 300000, "maintMarginRatio": 0.5, - "cum": 101795.0 + "cum": 101745.0 } } ], @@ -16239,110 +16224,6 @@ } } ], - "BDXN/USDT:USDT": [ - { - "tier": 1.0, - "symbol": "BDXN/USDT:USDT", - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.09, - "maxLeverage": 10.0, - "info": { - "bracket": 1, - "initialLeverage": 10, - "notionalCap": 10000, - "notionalFloor": 0, - "maintMarginRatio": 0.09, - "cum": 0.0 - } - }, - { - "tier": 2.0, - "symbol": "BDXN/USDT:USDT", - "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": 2, - "initialLeverage": 5, - "notionalCap": 50000, - "notionalFloor": 10000, - "maintMarginRatio": 0.1, - "cum": 100.0 - } - }, - { - "tier": 3.0, - "symbol": "BDXN/USDT:USDT", - "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 3, - "initialLeverage": 4, - "notionalCap": 100000, - "notionalFloor": 50000, - "maintMarginRatio": 0.125, - "cum": 1350.0 - } - }, - { - "tier": 4.0, - "symbol": "BDXN/USDT:USDT", - "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 4, - "initialLeverage": 3, - "notionalCap": 250000, - "notionalFloor": 100000, - "maintMarginRatio": 0.1667, - "cum": 5520.0 - } - }, - { - "tier": 5.0, - "symbol": "BDXN/USDT:USDT", - "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 5, - "initialLeverage": 2, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.25, - "cum": 26345.0 - } - }, - { - "tier": 6.0, - "symbol": "BDXN/USDT:USDT", - "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 800000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": 6, - "initialLeverage": 1, - "notionalCap": 800000, - "notionalFloor": 500000, - "maintMarginRatio": 0.5, - "cum": 151345.0 - } - } - ], "BEAMX/USDT:USDT": [ { "tier": 1.0, @@ -17161,14 +17042,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -17178,15 +17059,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, - "cum": 25.0 + "maintMarginRatio": 0.025, + "cum": 50.0 } }, { @@ -17194,33 +17075,33 @@ "symbol": "BIGTIME/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 25000, + "initialLeverage": 10, + "notionalCap": 20000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, - "cum": 75.0 + "maintMarginRatio": 0.05, + "cum": 300.0 } }, { "tier": 4.0, "symbol": "BIGTIME/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, + "minNotional": 20000.0, "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 4, - "initialLeverage": 20, + "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 25000, - "maintMarginRatio": 0.025, - "cum": 200.0 + "notionalFloor": 20000, + "maintMarginRatio": 0.1, + "cum": 1300.0 } }, { @@ -17228,88 +17109,54 @@ "symbol": "BIGTIME/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 125000, + "initialLeverage": 4, + "notionalCap": 250000, "notionalFloor": 50000, - "maintMarginRatio": 0.05, - "cum": 1450.0 + "maintMarginRatio": 0.125, + "cum": 2550.0 } }, { "tier": 6.0, "symbol": "BIGTIME/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.1, - "cum": 7700.0 + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 12975.0 } }, { "tier": 7.0, "symbol": "BIGTIME/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 7, - "initialLeverage": 4, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.125, - "cum": 13950.0 - } - }, - { - "tier": 8.0, - "symbol": "BIGTIME/USDT:USDT", - "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 8, - "initialLeverage": 3, - "notionalCap": 1000000, - "notionalFloor": 500000, - "maintMarginRatio": 0.1667, - "cum": 34800.0 - } - }, - { - "tier": 9.0, - "symbol": "BIGTIME/USDT:USDT", - "currency": "USDT", - "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 9, + "bracket": 7, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 1000000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 118100.0 + "cum": 54625.0 } }, { - "tier": 10.0, + "tier": 8.0, "symbol": "BIGTIME/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -17317,12 +17164,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 1993100.0 + "cum": 1929625.0 } } ], @@ -19316,14 +19163,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -19333,15 +19180,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, - "cum": 25.0 + "maintMarginRatio": 0.025, + "cum": 50.0 } }, { @@ -19349,33 +19196,33 @@ "symbol": "BOME/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 25000, + "initialLeverage": 10, + "notionalCap": 20000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, - "cum": 75.0 + "maintMarginRatio": 0.05, + "cum": 300.0 } }, { "tier": 4.0, "symbol": "BOME/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, + "minNotional": 20000.0, "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 4, - "initialLeverage": 20, + "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 25000, - "maintMarginRatio": 0.025, - "cum": 200.0 + "notionalFloor": 20000, + "maintMarginRatio": 0.1, + "cum": 1300.0 } }, { @@ -19383,88 +19230,54 @@ "symbol": "BOME/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 125000, + "initialLeverage": 4, + "notionalCap": 250000, "notionalFloor": 50000, - "maintMarginRatio": 0.05, - "cum": 1450.0 + "maintMarginRatio": 0.125, + "cum": 2550.0 } }, { "tier": 6.0, "symbol": "BOME/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.1, - "cum": 7700.0 + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 12975.0 } }, { "tier": 7.0, "symbol": "BOME/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 7, - "initialLeverage": 4, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.125, - "cum": 13950.0 - } - }, - { - "tier": 8.0, - "symbol": "BOME/USDT:USDT", - "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 8, - "initialLeverage": 3, - "notionalCap": 1000000, - "notionalFloor": 500000, - "maintMarginRatio": 0.1667, - "cum": 34800.0 - } - }, - { - "tier": 9.0, - "symbol": "BOME/USDT:USDT", - "currency": "USDT", - "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 9, + "bracket": 7, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 1000000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 118100.0 + "cum": 54625.0 } }, { - "tier": 10.0, + "tier": 8.0, "symbol": "BOME/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -19472,12 +19285,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 1993100.0 + "cum": 1929625.0 } } ], @@ -25868,14 +25681,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.025, "cum": 0.0 } }, @@ -25885,15 +25698,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 2, - "initialLeverage": 20, + "initialLeverage": 10, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.025, - "cum": 50.0 + "maintMarginRatio": 0.05, + "cum": 125.0 } }, { @@ -25901,101 +25714,84 @@ "symbol": "COOKIE/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 3, - "initialLeverage": 10, - "notionalCap": 20000, + "initialLeverage": 5, + "notionalCap": 60000, "notionalFloor": 10000, - "maintMarginRatio": 0.05, - "cum": 300.0 + "maintMarginRatio": 0.1, + "cum": 625.0 } }, { "tier": 4.0, "symbol": "COOKIE/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 60000.0, + "maxNotional": 70000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 4, - "initialLeverage": 5, - "notionalCap": 50000, - "notionalFloor": 20000, - "maintMarginRatio": 0.1, - "cum": 1300.0 + "initialLeverage": 4, + "notionalCap": 70000, + "notionalFloor": 60000, + "maintMarginRatio": 0.125, + "cum": 2125.0 } }, { "tier": 5.0, "symbol": "COOKIE/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 100000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 70000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 5, - "initialLeverage": 4, - "notionalCap": 100000, - "notionalFloor": 50000, - "maintMarginRatio": 0.125, - "cum": 2550.0 + "initialLeverage": 3, + "notionalCap": 250000, + "notionalFloor": 70000, + "maintMarginRatio": 0.1667, + "cum": 5044.0 } }, { "tier": 6.0, "symbol": "COOKIE/USDT:USDT", "currency": "USDT", - "minNotional": 100000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 6, - "initialLeverage": 3, - "notionalCap": 250000, - "notionalFloor": 100000, - "maintMarginRatio": 0.1667, - "cum": 6720.0 + "initialLeverage": 2, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.25, + "cum": 25869.0 } }, { "tier": 7.0, "symbol": "COOKIE/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 2500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 2500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.25, - "cum": 27545.0 - } - }, - { - "tier": 8.0, - "symbol": "COOKIE/USDT:USDT", - "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 500000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 7, "initialLeverage": 1, - "notionalCap": 5000000, - "notionalFloor": 2500000, + "notionalCap": 800000, + "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 652545.0 + "cum": 150869.0 } } ], @@ -28920,15 +28716,15 @@ "symbol": "DEGEN/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.04, - "maxLeverage": 20.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 1, - "initialLeverage": 20, - "notionalCap": 5000, + "initialLeverage": 10, + "notionalCap": 10000, "notionalFloor": 0, - "maintMarginRatio": 0.04, + "maintMarginRatio": 0.05, "cum": 0.0 } }, @@ -28936,38 +28732,21 @@ "tier": 2.0, "symbol": "DEGEN/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": 2, - "initialLeverage": 10, - "notionalCap": 10000, - "notionalFloor": 5000, - "maintMarginRatio": 0.05, - "cum": 50.0 - } - }, - { - "tier": 3.0, - "symbol": "DEGEN/USDT:USDT", - "currency": "USDT", "minNotional": 10000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": 3, + "bracket": 2, "initialLeverage": 5, "notionalCap": 50000, "notionalFloor": 10000, "maintMarginRatio": 0.1, - "cum": 550.0 + "cum": 500.0 } }, { - "tier": 4.0, + "tier": 3.0, "symbol": "DEGEN/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, @@ -28975,16 +28754,16 @@ "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 4, + "bracket": 3, "initialLeverage": 4, "notionalCap": 100000, "notionalFloor": 50000, "maintMarginRatio": 0.125, - "cum": 1800.0 + "cum": 1750.0 } }, { - "tier": 5.0, + "tier": 4.0, "symbol": "DEGEN/USDT:USDT", "currency": "USDT", "minNotional": 100000.0, @@ -28992,16 +28771,16 @@ "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 5, + "bracket": 4, "initialLeverage": 3, "notionalCap": 250000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 5970.0 + "cum": 5920.0 } }, { - "tier": 6.0, + "tier": 5.0, "symbol": "DEGEN/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, @@ -29009,16 +28788,16 @@ "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 6, + "bracket": 5, "initialLeverage": 2, "notionalCap": 400000, "notionalFloor": 250000, "maintMarginRatio": 0.25, - "cum": 26795.0 + "cum": 26745.0 } }, { - "tier": 7.0, + "tier": 6.0, "symbol": "DEGEN/USDT:USDT", "currency": "USDT", "minNotional": 400000.0, @@ -29026,12 +28805,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 7, + "bracket": 6, "initialLeverage": 1, "notionalCap": 500000, "notionalFloor": 400000, "maintMarginRatio": 0.5, - "cum": 126795.0 + "cum": 126745.0 } } ], @@ -40696,14 +40475,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -40713,15 +40492,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, - "cum": 25.0 + "maintMarginRatio": 0.025, + "cum": 50.0 } }, { @@ -40729,33 +40508,33 @@ "symbol": "GMT/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 25000, + "initialLeverage": 10, + "notionalCap": 20000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, - "cum": 75.0 + "maintMarginRatio": 0.05, + "cum": 300.0 } }, { "tier": 4.0, "symbol": "GMT/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, + "minNotional": 20000.0, "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 4, - "initialLeverage": 20, + "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 25000, - "maintMarginRatio": 0.025, - "cum": 200.0 + "notionalFloor": 20000, + "maintMarginRatio": 0.1, + "cum": 1300.0 } }, { @@ -40763,88 +40542,54 @@ "symbol": "GMT/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 125000, + "initialLeverage": 4, + "notionalCap": 250000, "notionalFloor": 50000, - "maintMarginRatio": 0.05, - "cum": 1450.0 + "maintMarginRatio": 0.125, + "cum": 2550.0 } }, { "tier": 6.0, "symbol": "GMT/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.1, - "cum": 7700.0 + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 12975.0 } }, { "tier": 7.0, "symbol": "GMT/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 7, - "initialLeverage": 4, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.125, - "cum": 13950.0 - } - }, - { - "tier": 8.0, - "symbol": "GMT/USDT:USDT", - "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 8, - "initialLeverage": 3, - "notionalCap": 1000000, - "notionalFloor": 500000, - "maintMarginRatio": 0.1667, - "cum": 34800.0 - } - }, - { - "tier": 9.0, - "symbol": "GMT/USDT:USDT", - "currency": "USDT", - "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 9, + "bracket": 7, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 1000000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 118100.0 + "cum": 54625.0 } }, { - "tier": 10.0, + "tier": 8.0, "symbol": "GMT/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -40852,12 +40597,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 1993100.0 + "cum": 1929625.0 } } ], @@ -43560,14 +43305,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.025, "cum": 0.0 } }, @@ -43577,15 +43322,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 2, - "initialLeverage": 20, + "initialLeverage": 10, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.025, - "cum": 50.0 + "maintMarginRatio": 0.05, + "cum": 125.0 } }, { @@ -43593,50 +43338,50 @@ "symbol": "HIGH/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 3, - "initialLeverage": 10, - "notionalCap": 20000, + "initialLeverage": 5, + "notionalCap": 60000, "notionalFloor": 10000, - "maintMarginRatio": 0.05, - "cum": 300.0 + "maintMarginRatio": 0.1, + "cum": 625.0 } }, { "tier": 4.0, "symbol": "HIGH/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 60000.0, + "maxNotional": 70000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 4, - "initialLeverage": 5, - "notionalCap": 50000, - "notionalFloor": 20000, - "maintMarginRatio": 0.1, - "cum": 1300.0 + "initialLeverage": 4, + "notionalCap": 70000, + "notionalFloor": 60000, + "maintMarginRatio": 0.125, + "cum": 2125.0 } }, { "tier": 5.0, "symbol": "HIGH/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, + "minNotional": 70000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 5, - "initialLeverage": 4, + "initialLeverage": 3, "notionalCap": 250000, - "notionalFloor": 50000, - "maintMarginRatio": 0.125, - "cum": 2550.0 + "notionalFloor": 70000, + "maintMarginRatio": 0.1667, + "cum": 5044.0 } }, { @@ -43645,15 +43390,15 @@ "currency": "USDT", "minNotional": 250000.0, "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 6, - "initialLeverage": 3, + "initialLeverage": 2, "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1667, - "cum": 12975.0 + "maintMarginRatio": 0.25, + "cum": 25869.0 } }, { @@ -43661,33 +43406,16 @@ "symbol": "HIGH/USDT:USDT", "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 7500000, - "notionalFloor": 500000, - "maintMarginRatio": 0.25, - "cum": 54625.0 - } - }, - { - "tier": 8.0, - "symbol": "HIGH/USDT:USDT", - "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 12500000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 7, "initialLeverage": 1, - "notionalCap": 12500000, - "notionalFloor": 7500000, + "notionalCap": 800000, + "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 1929625.0 + "cum": 150869.0 } } ], @@ -44078,14 +43806,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -44095,15 +43823,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, - "cum": 25.0 + "maintMarginRatio": 0.025, + "cum": 50.0 } }, { @@ -44111,33 +43839,33 @@ "symbol": "HOLO/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 25000, + "initialLeverage": 10, + "notionalCap": 20000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, - "cum": 75.0 + "maintMarginRatio": 0.05, + "cum": 300.0 } }, { "tier": 4.0, "symbol": "HOLO/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, + "minNotional": 20000.0, "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 4, - "initialLeverage": 20, + "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 25000, - "maintMarginRatio": 0.025, - "cum": 200.0 + "notionalFloor": 20000, + "maintMarginRatio": 0.1, + "cum": 1300.0 } }, { @@ -44145,88 +43873,54 @@ "symbol": "HOLO/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 125000, + "initialLeverage": 4, + "notionalCap": 250000, "notionalFloor": 50000, - "maintMarginRatio": 0.05, - "cum": 1450.0 + "maintMarginRatio": 0.125, + "cum": 2550.0 } }, { "tier": 6.0, "symbol": "HOLO/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.1, - "cum": 7700.0 + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 12975.0 } }, { "tier": 7.0, "symbol": "HOLO/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 7, - "initialLeverage": 4, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.125, - "cum": 13950.0 - } - }, - { - "tier": 8.0, - "symbol": "HOLO/USDT:USDT", - "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 8, - "initialLeverage": 3, - "notionalCap": 1000000, - "notionalFloor": 500000, - "maintMarginRatio": 0.1667, - "cum": 34800.0 - } - }, - { - "tier": 9.0, - "symbol": "HOLO/USDT:USDT", - "currency": "USDT", - "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 9, + "bracket": 7, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 1000000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 118100.0 + "cum": 54625.0 } }, { - "tier": 10.0, + "tier": 8.0, "symbol": "HOLO/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -44234,12 +43928,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 1993100.0 + "cum": 1929625.0 } } ], @@ -47180,14 +46874,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -47197,15 +46891,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, - "cum": 25.0 + "maintMarginRatio": 0.025, + "cum": 50.0 } }, { @@ -47213,33 +46907,33 @@ "symbol": "IO/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 25000, + "initialLeverage": 10, + "notionalCap": 20000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, - "cum": 75.0 + "maintMarginRatio": 0.05, + "cum": 300.0 } }, { "tier": 4.0, "symbol": "IO/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, + "minNotional": 20000.0, "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 4, - "initialLeverage": 20, + "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 25000, - "maintMarginRatio": 0.025, - "cum": 200.0 + "notionalFloor": 20000, + "maintMarginRatio": 0.1, + "cum": 1300.0 } }, { @@ -47247,88 +46941,54 @@ "symbol": "IO/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 125000, + "initialLeverage": 4, + "notionalCap": 250000, "notionalFloor": 50000, - "maintMarginRatio": 0.05, - "cum": 1450.0 + "maintMarginRatio": 0.125, + "cum": 2550.0 } }, { "tier": 6.0, "symbol": "IO/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.1, - "cum": 7700.0 + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 12975.0 } }, { "tier": 7.0, "symbol": "IO/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 7, - "initialLeverage": 4, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.125, - "cum": 13950.0 - } - }, - { - "tier": 8.0, - "symbol": "IO/USDT:USDT", - "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 8, - "initialLeverage": 3, - "notionalCap": 1000000, - "notionalFloor": 500000, - "maintMarginRatio": 0.1667, - "cum": 34800.0 - } - }, - { - "tier": 9.0, - "symbol": "IO/USDT:USDT", - "currency": "USDT", - "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 9, + "bracket": 7, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 1000000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 118100.0 + "cum": 54625.0 } }, { - "tier": 10.0, + "tier": 8.0, "symbol": "IO/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -47336,12 +46996,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 1993100.0 + "cum": 1929625.0 } } ], @@ -48194,15 +47854,15 @@ "symbol": "IR/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.04, - "maxLeverage": 20.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 1, - "initialLeverage": 20, - "notionalCap": 5000, + "initialLeverage": 10, + "notionalCap": 10000, "notionalFloor": 0, - "maintMarginRatio": 0.04, + "maintMarginRatio": 0.05, "cum": 0.0 } }, @@ -48210,38 +47870,21 @@ "tier": 2.0, "symbol": "IR/USDT:USDT", "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": 2, - "initialLeverage": 10, - "notionalCap": 10000, - "notionalFloor": 5000, - "maintMarginRatio": 0.05, - "cum": 50.0 - } - }, - { - "tier": 3.0, - "symbol": "IR/USDT:USDT", - "currency": "USDT", "minNotional": 10000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": 3, + "bracket": 2, "initialLeverage": 5, "notionalCap": 50000, "notionalFloor": 10000, "maintMarginRatio": 0.1, - "cum": 550.0 + "cum": 500.0 } }, { - "tier": 4.0, + "tier": 3.0, "symbol": "IR/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, @@ -48249,16 +47892,16 @@ "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 4, + "bracket": 3, "initialLeverage": 4, "notionalCap": 100000, "notionalFloor": 50000, "maintMarginRatio": 0.125, - "cum": 1800.0 + "cum": 1750.0 } }, { - "tier": 5.0, + "tier": 4.0, "symbol": "IR/USDT:USDT", "currency": "USDT", "minNotional": 100000.0, @@ -48266,16 +47909,16 @@ "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 5, + "bracket": 4, "initialLeverage": 3, "notionalCap": 250000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 5970.0 + "cum": 5920.0 } }, { - "tier": 6.0, + "tier": 5.0, "symbol": "IR/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, @@ -48283,16 +47926,16 @@ "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 6, + "bracket": 5, "initialLeverage": 2, "notionalCap": 400000, "notionalFloor": 250000, "maintMarginRatio": 0.25, - "cum": 26795.0 + "cum": 26745.0 } }, { - "tier": 7.0, + "tier": 6.0, "symbol": "IR/USDT:USDT", "currency": "USDT", "minNotional": 400000.0, @@ -48300,12 +47943,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 7, + "bracket": 6, "initialLeverage": 1, "notionalCap": 500000, "notionalFloor": 400000, "maintMarginRatio": 0.5, - "cum": 126795.0 + "cum": 126745.0 } } ], @@ -52125,14 +51768,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -52142,15 +51785,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, - "cum": 25.0 + "maintMarginRatio": 0.025, + "cum": 50.0 } }, { @@ -52158,33 +51801,33 @@ "symbol": "LAYER/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 25000, + "initialLeverage": 10, + "notionalCap": 20000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, - "cum": 75.0 + "maintMarginRatio": 0.05, + "cum": 300.0 } }, { "tier": 4.0, "symbol": "LAYER/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, + "minNotional": 20000.0, "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 4, - "initialLeverage": 20, + "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 25000, - "maintMarginRatio": 0.025, - "cum": 200.0 + "notionalFloor": 20000, + "maintMarginRatio": 0.1, + "cum": 1300.0 } }, { @@ -52192,88 +51835,54 @@ "symbol": "LAYER/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 125000, + "initialLeverage": 4, + "notionalCap": 250000, "notionalFloor": 50000, - "maintMarginRatio": 0.05, - "cum": 1450.0 + "maintMarginRatio": 0.125, + "cum": 2550.0 } }, { "tier": 6.0, "symbol": "LAYER/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.1, - "cum": 7700.0 + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 12975.0 } }, { "tier": 7.0, "symbol": "LAYER/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 7, - "initialLeverage": 4, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.125, - "cum": 13950.0 - } - }, - { - "tier": 8.0, - "symbol": "LAYER/USDT:USDT", - "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 8, - "initialLeverage": 3, - "notionalCap": 1000000, - "notionalFloor": 500000, - "maintMarginRatio": 0.1667, - "cum": 34800.0 - } - }, - { - "tier": 9.0, - "symbol": "LAYER/USDT:USDT", - "currency": "USDT", - "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 9, + "bracket": 7, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 1000000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 118100.0 + "cum": 54625.0 } }, { - "tier": 10.0, + "tier": 8.0, "symbol": "LAYER/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -52281,12 +51890,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 1993100.0 + "cum": 1929625.0 } } ], @@ -57125,14 +56734,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -57142,15 +56751,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, - "cum": 25.0 + "maintMarginRatio": 0.025, + "cum": 50.0 } }, { @@ -57158,33 +56767,33 @@ "symbol": "MEME/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 25000, + "initialLeverage": 10, + "notionalCap": 20000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, - "cum": 75.0 + "maintMarginRatio": 0.05, + "cum": 300.0 } }, { "tier": 4.0, "symbol": "MEME/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, + "minNotional": 20000.0, "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 4, - "initialLeverage": 20, + "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 25000, - "maintMarginRatio": 0.025, - "cum": 200.0 + "notionalFloor": 20000, + "maintMarginRatio": 0.1, + "cum": 1300.0 } }, { @@ -57192,88 +56801,54 @@ "symbol": "MEME/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 125000, + "initialLeverage": 4, + "notionalCap": 250000, "notionalFloor": 50000, - "maintMarginRatio": 0.05, - "cum": 1450.0 + "maintMarginRatio": 0.125, + "cum": 2550.0 } }, { "tier": 6.0, "symbol": "MEME/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.1, - "cum": 7700.0 + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 12975.0 } }, { "tier": 7.0, "symbol": "MEME/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 7, - "initialLeverage": 4, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.125, - "cum": 13950.0 - } - }, - { - "tier": 8.0, - "symbol": "MEME/USDT:USDT", - "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 8, - "initialLeverage": 3, - "notionalCap": 1000000, - "notionalFloor": 500000, - "maintMarginRatio": 0.1667, - "cum": 34800.0 - } - }, - { - "tier": 9.0, - "symbol": "MEME/USDT:USDT", - "currency": "USDT", - "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 9, + "bracket": 7, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 1000000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 118100.0 + "cum": 54625.0 } }, { - "tier": 10.0, + "tier": 8.0, "symbol": "MEME/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -57281,12 +56856,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 1993100.0 + "cum": 1929625.0 } } ], @@ -57418,14 +56993,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -57435,15 +57010,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, - "cum": 25.0 + "maintMarginRatio": 0.025, + "cum": 50.0 } }, { @@ -57451,33 +57026,33 @@ "symbol": "MERL/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 25000, + "initialLeverage": 10, + "notionalCap": 20000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, - "cum": 75.0 + "maintMarginRatio": 0.05, + "cum": 300.0 } }, { "tier": 4.0, "symbol": "MERL/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, + "minNotional": 20000.0, "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 4, - "initialLeverage": 20, + "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 25000, - "maintMarginRatio": 0.025, - "cum": 200.0 + "notionalFloor": 20000, + "maintMarginRatio": 0.1, + "cum": 1300.0 } }, { @@ -57485,88 +57060,54 @@ "symbol": "MERL/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 125000, + "initialLeverage": 4, + "notionalCap": 250000, "notionalFloor": 50000, - "maintMarginRatio": 0.05, - "cum": 1450.0 + "maintMarginRatio": 0.125, + "cum": 2550.0 } }, { "tier": 6.0, "symbol": "MERL/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.1, - "cum": 7700.0 + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 12975.0 } }, { "tier": 7.0, "symbol": "MERL/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 7, - "initialLeverage": 4, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.125, - "cum": 13950.0 - } - }, - { - "tier": 8.0, - "symbol": "MERL/USDT:USDT", - "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 8, - "initialLeverage": 3, - "notionalCap": 1000000, - "notionalFloor": 500000, - "maintMarginRatio": 0.1667, - "cum": 34800.0 - } - }, - { - "tier": 9.0, - "symbol": "MERL/USDT:USDT", - "currency": "USDT", - "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 9, + "bracket": 7, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 1000000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 118100.0 + "cum": 54625.0 } }, { - "tier": 10.0, + "tier": 8.0, "symbol": "MERL/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -57574,12 +57115,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 1993100.0 + "cum": 1929625.0 } } ], @@ -58866,14 +58407,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.025, "cum": 0.0 } }, @@ -58883,15 +58424,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 2, - "initialLeverage": 20, + "initialLeverage": 10, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.025, - "cum": 50.0 + "maintMarginRatio": 0.05, + "cum": 125.0 } }, { @@ -58899,50 +58440,50 @@ "symbol": "MLN/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 3, - "initialLeverage": 10, - "notionalCap": 20000, + "initialLeverage": 5, + "notionalCap": 60000, "notionalFloor": 10000, - "maintMarginRatio": 0.05, - "cum": 300.0 + "maintMarginRatio": 0.1, + "cum": 625.0 } }, { "tier": 4.0, "symbol": "MLN/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 60000.0, + "maxNotional": 70000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 4, - "initialLeverage": 5, - "notionalCap": 50000, - "notionalFloor": 20000, - "maintMarginRatio": 0.1, - "cum": 1300.0 + "initialLeverage": 4, + "notionalCap": 70000, + "notionalFloor": 60000, + "maintMarginRatio": 0.125, + "cum": 2125.0 } }, { "tier": 5.0, "symbol": "MLN/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, + "minNotional": 70000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 5, - "initialLeverage": 4, + "initialLeverage": 3, "notionalCap": 250000, - "notionalFloor": 50000, - "maintMarginRatio": 0.125, - "cum": 2550.0 + "notionalFloor": 70000, + "maintMarginRatio": 0.1667, + "cum": 5044.0 } }, { @@ -58951,15 +58492,15 @@ "currency": "USDT", "minNotional": 250000.0, "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 6, - "initialLeverage": 3, + "initialLeverage": 2, "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1667, - "cum": 12975.0 + "maintMarginRatio": 0.25, + "cum": 25869.0 } }, { @@ -58967,33 +58508,16 @@ "symbol": "MLN/USDT:USDT", "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 7500000, - "notionalFloor": 500000, - "maintMarginRatio": 0.25, - "cum": 54625.0 - } - }, - { - "tier": 8.0, - "symbol": "MLN/USDT:USDT", - "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 12500000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 7, "initialLeverage": 1, - "notionalCap": 12500000, - "notionalFloor": 7500000, + "notionalCap": 800000, + "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 1929625.0 + "cum": 150869.0 } } ], @@ -61556,14 +61080,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -61573,15 +61097,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, - "cum": 25.0 + "maintMarginRatio": 0.025, + "cum": 50.0 } }, { @@ -61589,33 +61113,33 @@ "symbol": "NEIRO/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 25000, + "initialLeverage": 10, + "notionalCap": 20000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, - "cum": 75.0 + "maintMarginRatio": 0.05, + "cum": 300.0 } }, { "tier": 4.0, "symbol": "NEIRO/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, + "minNotional": 20000.0, "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 4, - "initialLeverage": 20, + "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 25000, - "maintMarginRatio": 0.025, - "cum": 200.0 + "notionalFloor": 20000, + "maintMarginRatio": 0.1, + "cum": 1300.0 } }, { @@ -61623,88 +61147,54 @@ "symbol": "NEIRO/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 125000, + "initialLeverage": 4, + "notionalCap": 250000, "notionalFloor": 50000, - "maintMarginRatio": 0.05, - "cum": 1450.0 + "maintMarginRatio": 0.125, + "cum": 2550.0 } }, { "tier": 6.0, "symbol": "NEIRO/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.1, - "cum": 7700.0 + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 12975.0 } }, { "tier": 7.0, "symbol": "NEIRO/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 7, - "initialLeverage": 4, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.125, - "cum": 13950.0 - } - }, - { - "tier": 8.0, - "symbol": "NEIRO/USDT:USDT", - "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 8, - "initialLeverage": 3, - "notionalCap": 1000000, - "notionalFloor": 500000, - "maintMarginRatio": 0.1667, - "cum": 34800.0 - } - }, - { - "tier": 9.0, - "symbol": "NEIRO/USDT:USDT", - "currency": "USDT", - "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 9, + "bracket": 7, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 1000000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 118100.0 + "cum": 54625.0 } }, { - "tier": 10.0, + "tier": 8.0, "symbol": "NEIRO/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -61712,12 +61202,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 1993100.0 + "cum": 1929625.0 } } ], @@ -63004,6 +62494,127 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, + "info": { + "bracket": 1, + "initialLeverage": 20, + "notionalCap": 5000, + "notionalFloor": 0, + "maintMarginRatio": 0.025, + "cum": 0.0 + } + }, + { + "tier": 2.0, + "symbol": "NOM/USDT:USDT", + "currency": "USDT", + "minNotional": 5000.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, + "info": { + "bracket": 2, + "initialLeverage": 10, + "notionalCap": 10000, + "notionalFloor": 5000, + "maintMarginRatio": 0.05, + "cum": 125.0 + } + }, + { + "tier": 3.0, + "symbol": "NOM/USDT:USDT", + "currency": "USDT", + "minNotional": 10000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, + "info": { + "bracket": 3, + "initialLeverage": 5, + "notionalCap": 60000, + "notionalFloor": 10000, + "maintMarginRatio": 0.1, + "cum": 625.0 + } + }, + { + "tier": 4.0, + "symbol": "NOM/USDT:USDT", + "currency": "USDT", + "minNotional": 60000.0, + "maxNotional": 70000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, + "info": { + "bracket": 4, + "initialLeverage": 4, + "notionalCap": 70000, + "notionalFloor": 60000, + "maintMarginRatio": 0.125, + "cum": 2125.0 + } + }, + { + "tier": 5.0, + "symbol": "NOM/USDT:USDT", + "currency": "USDT", + "minNotional": 70000.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, + "info": { + "bracket": 5, + "initialLeverage": 3, + "notionalCap": 250000, + "notionalFloor": 70000, + "maintMarginRatio": 0.1667, + "cum": 5044.0 + } + }, + { + "tier": 6.0, + "symbol": "NOM/USDT:USDT", + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 6, + "initialLeverage": 2, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.25, + "cum": 25869.0 + } + }, + { + "tier": 7.0, + "symbol": "NOM/USDT:USDT", + "currency": "USDT", + "minNotional": 500000.0, + "maxNotional": 800000.0, + "maintenanceMarginRate": 0.5, + "maxLeverage": 1.0, + "info": { + "bracket": 7, + "initialLeverage": 1, + "notionalCap": 800000, + "notionalFloor": 500000, + "maintMarginRatio": 0.5, + "cum": 150869.0 + } + } + ], + "NOT/USDT:USDT": [ + { + "tier": 1.0, + "symbol": "NOT/USDT:USDT", + "currency": "USDT", + "minNotional": 0.0, + "maxNotional": 5000.0, "maintenanceMarginRate": 0.015, "maxLeverage": 50.0, "info": { @@ -63017,7 +62628,7 @@ }, { "tier": 2.0, - "symbol": "NOM/USDT:USDT", + "symbol": "NOT/USDT:USDT", "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, @@ -63034,7 +62645,7 @@ }, { "tier": 3.0, - "symbol": "NOM/USDT:USDT", + "symbol": "NOT/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, "maxNotional": 20000.0, @@ -63051,7 +62662,7 @@ }, { "tier": 4.0, - "symbol": "NOM/USDT:USDT", + "symbol": "NOT/USDT:USDT", "currency": "USDT", "minNotional": 20000.0, "maxNotional": 50000.0, @@ -63068,7 +62679,7 @@ }, { "tier": 5.0, - "symbol": "NOM/USDT:USDT", + "symbol": "NOT/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, "maxNotional": 250000.0, @@ -63085,7 +62696,7 @@ }, { "tier": 6.0, - "symbol": "NOM/USDT:USDT", + "symbol": "NOT/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, "maxNotional": 500000.0, @@ -63102,7 +62713,7 @@ }, { "tier": 7.0, - "symbol": "NOM/USDT:USDT", + "symbol": "NOT/USDT:USDT", "currency": "USDT", "minNotional": 500000.0, "maxNotional": 7500000.0, @@ -63119,7 +62730,7 @@ }, { "tier": 8.0, - "symbol": "NOM/USDT:USDT", + "symbol": "NOT/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, "maxNotional": 12500000.0, @@ -63135,178 +62746,6 @@ } } ], - "NOT/USDT:USDT": [ - { - "tier": 1.0, - "symbol": "NOT/USDT:USDT", - "currency": "USDT", - "minNotional": 0.0, - "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, - "info": { - "bracket": 1, - "initialLeverage": 75, - "notionalCap": 5000, - "notionalFloor": 0, - "maintMarginRatio": 0.01, - "cum": 0.0 - } - }, - { - "tier": 2.0, - "symbol": "NOT/USDT:USDT", - "currency": "USDT", - "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, - "info": { - "bracket": 2, - "initialLeverage": 50, - "notionalCap": 10000, - "notionalFloor": 5000, - "maintMarginRatio": 0.015, - "cum": 25.0 - } - }, - { - "tier": 3.0, - "symbol": "NOT/USDT:USDT", - "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, - "info": { - "bracket": 3, - "initialLeverage": 25, - "notionalCap": 25000, - "notionalFloor": 10000, - "maintMarginRatio": 0.02, - "cum": 75.0 - } - }, - { - "tier": 4.0, - "symbol": "NOT/USDT:USDT", - "currency": "USDT", - "minNotional": 25000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, - "info": { - "bracket": 4, - "initialLeverage": 20, - "notionalCap": 50000, - "notionalFloor": 25000, - "maintMarginRatio": 0.025, - "cum": 200.0 - } - }, - { - "tier": 5.0, - "symbol": "NOT/USDT:USDT", - "currency": "USDT", - "minNotional": 50000.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": 5, - "initialLeverage": 10, - "notionalCap": 125000, - "notionalFloor": 50000, - "maintMarginRatio": 0.05, - "cum": 1450.0 - } - }, - { - "tier": 6.0, - "symbol": "NOT/USDT:USDT", - "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": 6, - "initialLeverage": 5, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.1, - "cum": 7700.0 - } - }, - { - "tier": 7.0, - "symbol": "NOT/USDT:USDT", - "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 7, - "initialLeverage": 4, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.125, - "cum": 13950.0 - } - }, - { - "tier": 8.0, - "symbol": "NOT/USDT:USDT", - "currency": "USDT", - "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 8, - "initialLeverage": 3, - "notionalCap": 1000000, - "notionalFloor": 500000, - "maintMarginRatio": 0.1667, - "cum": 34800.0 - } - }, - { - "tier": 9.0, - "symbol": "NOT/USDT:USDT", - "currency": "USDT", - "minNotional": 1000000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 9, - "initialLeverage": 2, - "notionalCap": 7500000, - "notionalFloor": 1000000, - "maintMarginRatio": 0.25, - "cum": 118100.0 - } - }, - { - "tier": 10.0, - "symbol": "NOT/USDT:USDT", - "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 12500000.0, - "maintenanceMarginRate": 0.5, - "maxLeverage": 1.0, - "info": { - "bracket": 10, - "initialLeverage": 1, - "notionalCap": 12500000, - "notionalFloor": 7500000, - "maintMarginRatio": 0.5, - "cum": 1993100.0 - } - } - ], "NTRN/USDT:USDT": [ { "tier": 1.0, @@ -69607,14 +69046,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -69624,15 +69063,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, - "cum": 25.0 + "maintMarginRatio": 0.025, + "cum": 50.0 } }, { @@ -69640,33 +69079,33 @@ "symbol": "PNUT/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 25000, + "initialLeverage": 10, + "notionalCap": 20000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, - "cum": 75.0 + "maintMarginRatio": 0.05, + "cum": 300.0 } }, { "tier": 4.0, "symbol": "PNUT/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, + "minNotional": 20000.0, "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 4, - "initialLeverage": 20, + "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 25000, - "maintMarginRatio": 0.025, - "cum": 200.0 + "notionalFloor": 20000, + "maintMarginRatio": 0.1, + "cum": 1300.0 } }, { @@ -69674,88 +69113,54 @@ "symbol": "PNUT/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 125000, + "initialLeverage": 4, + "notionalCap": 250000, "notionalFloor": 50000, - "maintMarginRatio": 0.05, - "cum": 1450.0 + "maintMarginRatio": 0.125, + "cum": 2550.0 } }, { "tier": 6.0, "symbol": "PNUT/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.1, - "cum": 7700.0 + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 12975.0 } }, { "tier": 7.0, "symbol": "PNUT/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 7, - "initialLeverage": 4, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.125, - "cum": 13950.0 - } - }, - { - "tier": 8.0, - "symbol": "PNUT/USDT:USDT", - "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 8, - "initialLeverage": 3, - "notionalCap": 1000000, - "notionalFloor": 500000, - "maintMarginRatio": 0.1667, - "cum": 34800.0 - } - }, - { - "tier": 9.0, - "symbol": "PNUT/USDT:USDT", - "currency": "USDT", - "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 9, + "bracket": 7, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 1000000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 118100.0 + "cum": 54625.0 } }, { - "tier": 10.0, + "tier": 8.0, "symbol": "PNUT/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -69763,12 +69168,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 1993100.0 + "cum": 1929625.0 } } ], @@ -80832,14 +80237,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.01, - "maxLeverage": 75.0, + "maintenanceMarginRate": 0.015, + "maxLeverage": 50.0, "info": { "bracket": 1, - "initialLeverage": 75, + "initialLeverage": 50, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.01, + "maintMarginRatio": 0.015, "cum": 0.0 } }, @@ -80849,15 +80254,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 2, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.015, - "cum": 25.0 + "maintMarginRatio": 0.025, + "cum": 50.0 } }, { @@ -80865,33 +80270,33 @@ "symbol": "SOMI/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 25000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 25.0, + "maxNotional": 20000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 3, - "initialLeverage": 25, - "notionalCap": 25000, + "initialLeverage": 10, + "notionalCap": 20000, "notionalFloor": 10000, - "maintMarginRatio": 0.02, - "cum": 75.0 + "maintMarginRatio": 0.05, + "cum": 300.0 } }, { "tier": 4.0, "symbol": "SOMI/USDT:USDT", "currency": "USDT", - "minNotional": 25000.0, + "minNotional": 20000.0, "maxNotional": 50000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 4, - "initialLeverage": 20, + "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 25000, - "maintMarginRatio": 0.025, - "cum": 200.0 + "notionalFloor": 20000, + "maintMarginRatio": 0.1, + "cum": 1300.0 } }, { @@ -80899,88 +80304,54 @@ "symbol": "SOMI/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, - "maxNotional": 125000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 250000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 5, - "initialLeverage": 10, - "notionalCap": 125000, + "initialLeverage": 4, + "notionalCap": 250000, "notionalFloor": 50000, - "maintMarginRatio": 0.05, - "cum": 1450.0 + "maintMarginRatio": 0.125, + "cum": 2550.0 } }, { "tier": 6.0, "symbol": "SOMI/USDT:USDT", "currency": "USDT", - "minNotional": 125000.0, - "maxNotional": 250000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 6, - "initialLeverage": 5, - "notionalCap": 250000, - "notionalFloor": 125000, - "maintMarginRatio": 0.1, - "cum": 7700.0 + "initialLeverage": 3, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.1667, + "cum": 12975.0 } }, { "tier": 7.0, "symbol": "SOMI/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 500000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, - "info": { - "bracket": 7, - "initialLeverage": 4, - "notionalCap": 500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.125, - "cum": 13950.0 - } - }, - { - "tier": 8.0, - "symbol": "SOMI/USDT:USDT", - "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 1000000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, - "info": { - "bracket": 8, - "initialLeverage": 3, - "notionalCap": 1000000, - "notionalFloor": 500000, - "maintMarginRatio": 0.1667, - "cum": 34800.0 - } - }, - { - "tier": 9.0, - "symbol": "SOMI/USDT:USDT", - "currency": "USDT", - "minNotional": 1000000.0, "maxNotional": 7500000.0, "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 9, + "bracket": 7, "initialLeverage": 2, "notionalCap": 7500000, - "notionalFloor": 1000000, + "notionalFloor": 500000, "maintMarginRatio": 0.25, - "cum": 118100.0 + "cum": 54625.0 } }, { - "tier": 10.0, + "tier": 8.0, "symbol": "SOMI/USDT:USDT", "currency": "USDT", "minNotional": 7500000.0, @@ -80988,12 +80359,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 10, + "bracket": 8, "initialLeverage": 1, "notionalCap": 12500000, "notionalFloor": 7500000, "maintMarginRatio": 0.5, - "cum": 1993100.0 + "cum": 1929625.0 } } ], @@ -93543,14 +92914,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.02, - "maxLeverage": 40.0, + "maintenanceMarginRate": 0.045, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 40, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.02, + "maintMarginRatio": 0.045, "cum": 0.0 } }, @@ -93560,14 +92931,14 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 2, - "initialLeverage": 20, + "initialLeverage": 10, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.025, + "maintMarginRatio": 0.05, "cum": 25.0 } }, @@ -93576,37 +92947,20 @@ "symbol": "VINE/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, - "info": { - "bracket": 3, - "initialLeverage": 10, - "notionalCap": 20000, - "notionalFloor": 10000, - "maintMarginRatio": 0.05, - "cum": 275.0 - } - }, - { - "tier": 4.0, - "symbol": "VINE/USDT:USDT", - "currency": "USDT", - "minNotional": 20000.0, "maxNotional": 50000.0, "maintenanceMarginRate": 0.1, "maxLeverage": 5.0, "info": { - "bracket": 4, + "bracket": 3, "initialLeverage": 5, "notionalCap": 50000, - "notionalFloor": 20000, + "notionalFloor": 10000, "maintMarginRatio": 0.1, - "cum": 1275.0 + "cum": 525.0 } }, { - "tier": 5.0, + "tier": 4.0, "symbol": "VINE/USDT:USDT", "currency": "USDT", "minNotional": 50000.0, @@ -93614,16 +92968,16 @@ "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 5, + "bracket": 4, "initialLeverage": 4, "notionalCap": 100000, "notionalFloor": 50000, "maintMarginRatio": 0.125, - "cum": 2525.0 + "cum": 1775.0 } }, { - "tier": 6.0, + "tier": 5.0, "symbol": "VINE/USDT:USDT", "currency": "USDT", "minNotional": 100000.0, @@ -93631,46 +92985,46 @@ "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 6, + "bracket": 5, "initialLeverage": 3, "notionalCap": 250000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 6695.0 + "cum": 5945.0 + } + }, + { + "tier": 6.0, + "symbol": "VINE/USDT:USDT", + "currency": "USDT", + "minNotional": 250000.0, + "maxNotional": 500000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, + "info": { + "bracket": 6, + "initialLeverage": 2, + "notionalCap": 500000, + "notionalFloor": 250000, + "maintMarginRatio": 0.25, + "cum": 26770.0 } }, { "tier": 7.0, "symbol": "VINE/USDT:USDT", "currency": "USDT", - "minNotional": 250000.0, - "maxNotional": 2500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 2500000, - "notionalFloor": 250000, - "maintMarginRatio": 0.25, - "cum": 27520.0 - } - }, - { - "tier": 8.0, - "symbol": "VINE/USDT:USDT", - "currency": "USDT", - "minNotional": 2500000.0, - "maxNotional": 5000000.0, + "minNotional": 500000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 7, "initialLeverage": 1, - "notionalCap": 5000000, - "notionalFloor": 2500000, + "notionalCap": 800000, + "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 652520.0 + "cum": 151770.0 } } ], @@ -99416,14 +98770,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.015, - "maxLeverage": 50.0, + "maintenanceMarginRate": 0.025, + "maxLeverage": 20.0, "info": { "bracket": 1, - "initialLeverage": 50, + "initialLeverage": 20, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.015, + "maintMarginRatio": 0.025, "cum": 0.0 } }, @@ -99433,15 +98787,15 @@ "currency": "USDT", "minNotional": 5000.0, "maxNotional": 10000.0, - "maintenanceMarginRate": 0.025, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 2, - "initialLeverage": 20, + "initialLeverage": 10, "notionalCap": 10000, "notionalFloor": 5000, - "maintMarginRatio": 0.025, - "cum": 50.0 + "maintMarginRatio": 0.05, + "cum": 125.0 } }, { @@ -99449,50 +98803,50 @@ "symbol": "YB/USDT:USDT", "currency": "USDT", "minNotional": 10000.0, - "maxNotional": 20000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 3, - "initialLeverage": 10, - "notionalCap": 20000, + "initialLeverage": 5, + "notionalCap": 60000, "notionalFloor": 10000, - "maintMarginRatio": 0.05, - "cum": 300.0 + "maintMarginRatio": 0.1, + "cum": 625.0 } }, { "tier": 4.0, "symbol": "YB/USDT:USDT", "currency": "USDT", - "minNotional": 20000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 60000.0, + "maxNotional": 70000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 4, - "initialLeverage": 5, - "notionalCap": 50000, - "notionalFloor": 20000, - "maintMarginRatio": 0.1, - "cum": 1300.0 + "initialLeverage": 4, + "notionalCap": 70000, + "notionalFloor": 60000, + "maintMarginRatio": 0.125, + "cum": 2125.0 } }, { "tier": 5.0, "symbol": "YB/USDT:USDT", "currency": "USDT", - "minNotional": 50000.0, + "minNotional": 70000.0, "maxNotional": 250000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 5, - "initialLeverage": 4, + "initialLeverage": 3, "notionalCap": 250000, - "notionalFloor": 50000, - "maintMarginRatio": 0.125, - "cum": 2550.0 + "notionalFloor": 70000, + "maintMarginRatio": 0.1667, + "cum": 5044.0 } }, { @@ -99501,15 +98855,15 @@ "currency": "USDT", "minNotional": 250000.0, "maxNotional": 500000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 6, - "initialLeverage": 3, + "initialLeverage": 2, "notionalCap": 500000, "notionalFloor": 250000, - "maintMarginRatio": 0.1667, - "cum": 12975.0 + "maintMarginRatio": 0.25, + "cum": 25869.0 } }, { @@ -99517,33 +98871,16 @@ "symbol": "YB/USDT:USDT", "currency": "USDT", "minNotional": 500000.0, - "maxNotional": 7500000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 7, - "initialLeverage": 2, - "notionalCap": 7500000, - "notionalFloor": 500000, - "maintMarginRatio": 0.25, - "cum": 54625.0 - } - }, - { - "tier": 8.0, - "symbol": "YB/USDT:USDT", - "currency": "USDT", - "minNotional": 7500000.0, - "maxNotional": 12500000.0, + "maxNotional": 800000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 8, + "bracket": 7, "initialLeverage": 1, - "notionalCap": 12500000, - "notionalFloor": 7500000, + "notionalCap": 800000, + "notionalFloor": 500000, "maintMarginRatio": 0.5, - "cum": 1929625.0 + "cum": 150869.0 } } ], @@ -101431,14 +100768,14 @@ "currency": "USDT", "minNotional": 0.0, "maxNotional": 5000.0, - "maintenanceMarginRate": 0.04, - "maxLeverage": 20.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 1, - "initialLeverage": 20, + "initialLeverage": 10, "notionalCap": 5000, "notionalFloor": 0, - "maintMarginRatio": 0.04, + "maintMarginRatio": 0.05, "cum": 0.0 } }, @@ -101447,54 +100784,37 @@ "symbol": "ZKJ/USDT:USDT", "currency": "USDT", "minNotional": 5000.0, - "maxNotional": 10000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "maxNotional": 40000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 2, - "initialLeverage": 10, - "notionalCap": 10000, + "initialLeverage": 5, + "notionalCap": 40000, "notionalFloor": 5000, - "maintMarginRatio": 0.05, - "cum": 50.0 + "maintMarginRatio": 0.1, + "cum": 250.0 } }, { "tier": 3.0, "symbol": "ZKJ/USDT:USDT", "currency": "USDT", - "minNotional": 10000.0, - "maxNotional": 50000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, - "info": { - "bracket": 3, - "initialLeverage": 5, - "notionalCap": 50000, - "notionalFloor": 10000, - "maintMarginRatio": 0.1, - "cum": 550.0 - } - }, - { - "tier": 4.0, - "symbol": "ZKJ/USDT:USDT", - "currency": "USDT", - "minNotional": 50000.0, + "minNotional": 40000.0, "maxNotional": 100000.0, "maintenanceMarginRate": 0.125, "maxLeverage": 4.0, "info": { - "bracket": 4, + "bracket": 3, "initialLeverage": 4, "notionalCap": 100000, - "notionalFloor": 50000, + "notionalFloor": 40000, "maintMarginRatio": 0.125, - "cum": 1800.0 + "cum": 1250.0 } }, { - "tier": 5.0, + "tier": 4.0, "symbol": "ZKJ/USDT:USDT", "currency": "USDT", "minNotional": 100000.0, @@ -101502,16 +100822,16 @@ "maintenanceMarginRate": 0.1667, "maxLeverage": 3.0, "info": { - "bracket": 5, + "bracket": 4, "initialLeverage": 3, "notionalCap": 250000, "notionalFloor": 100000, "maintMarginRatio": 0.1667, - "cum": 5970.0 + "cum": 5420.0 } }, { - "tier": 6.0, + "tier": 5.0, "symbol": "ZKJ/USDT:USDT", "currency": "USDT", "minNotional": 250000.0, @@ -101519,16 +100839,16 @@ "maintenanceMarginRate": 0.25, "maxLeverage": 2.0, "info": { - "bracket": 6, + "bracket": 5, "initialLeverage": 2, "notionalCap": 400000, "notionalFloor": 250000, "maintMarginRatio": 0.25, - "cum": 26795.0 + "cum": 26245.0 } }, { - "tier": 7.0, + "tier": 6.0, "symbol": "ZKJ/USDT:USDT", "currency": "USDT", "minNotional": 400000.0, @@ -101536,12 +100856,12 @@ "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 7, + "bracket": 6, "initialLeverage": 1, "notionalCap": 500000, "notionalFloor": 400000, "maintMarginRatio": 0.5, - "cum": 126795.0 + "cum": 126245.0 } } ], @@ -102309,15 +101629,15 @@ "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", "minNotional": 0.0, - "maxNotional": 2000.0, - "maintenanceMarginRate": 0.045, - "maxLeverage": 20.0, + "maxNotional": 10000.0, + "maintenanceMarginRate": 0.05, + "maxLeverage": 10.0, "info": { "bracket": 1, - "initialLeverage": 20, - "notionalCap": 2000, + "initialLeverage": 10, + "notionalCap": 10000, "notionalFloor": 0, - "maintMarginRatio": 0.045, + "maintMarginRatio": 0.05, "cum": 0.0 } }, @@ -102325,102 +101645,85 @@ "tier": 2.0, "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", - "minNotional": 2000.0, - "maxNotional": 15000.0, - "maintenanceMarginRate": 0.05, - "maxLeverage": 10.0, + "minNotional": 10000.0, + "maxNotional": 60000.0, + "maintenanceMarginRate": 0.1, + "maxLeverage": 5.0, "info": { "bracket": 2, - "initialLeverage": 10, - "notionalCap": 15000, - "notionalFloor": 2000, - "maintMarginRatio": 0.05, - "cum": 10.0 + "initialLeverage": 5, + "notionalCap": 60000, + "notionalFloor": 10000, + "maintMarginRatio": 0.1, + "cum": 500.0 } }, { "tier": 3.0, "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", - "minNotional": 15000.0, - "maxNotional": 60000.0, - "maintenanceMarginRate": 0.1, - "maxLeverage": 5.0, + "minNotional": 60000.0, + "maxNotional": 120000.0, + "maintenanceMarginRate": 0.125, + "maxLeverage": 4.0, "info": { "bracket": 3, - "initialLeverage": 5, - "notionalCap": 60000, - "notionalFloor": 15000, - "maintMarginRatio": 0.1, - "cum": 760.0 + "initialLeverage": 4, + "notionalCap": 120000, + "notionalFloor": 60000, + "maintMarginRatio": 0.125, + "cum": 2000.0 } }, { "tier": 4.0, "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", - "minNotional": 60000.0, - "maxNotional": 200000.0, - "maintenanceMarginRate": 0.125, - "maxLeverage": 4.0, + "minNotional": 120000.0, + "maxNotional": 300000.0, + "maintenanceMarginRate": 0.1667, + "maxLeverage": 3.0, "info": { "bracket": 4, - "initialLeverage": 4, - "notionalCap": 200000, - "notionalFloor": 60000, - "maintMarginRatio": 0.125, - "cum": 2260.0 + "initialLeverage": 3, + "notionalCap": 300000, + "notionalFloor": 120000, + "maintMarginRatio": 0.1667, + "cum": 7004.0 } }, { "tier": 5.0, "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", - "minNotional": 200000.0, - "maxNotional": 400000.0, - "maintenanceMarginRate": 0.1667, - "maxLeverage": 3.0, + "minNotional": 300000.0, + "maxNotional": 1000000.0, + "maintenanceMarginRate": 0.25, + "maxLeverage": 2.0, "info": { "bracket": 5, - "initialLeverage": 3, - "notionalCap": 400000, - "notionalFloor": 200000, - "maintMarginRatio": 0.1667, - "cum": 10600.0 + "initialLeverage": 2, + "notionalCap": 1000000, + "notionalFloor": 300000, + "maintMarginRatio": 0.25, + "cum": 31994.0 } }, { "tier": 6.0, "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", "currency": "USDT", - "minNotional": 400000.0, - "maxNotional": 1200000.0, - "maintenanceMarginRate": 0.25, - "maxLeverage": 2.0, - "info": { - "bracket": 6, - "initialLeverage": 2, - "notionalCap": 1200000, - "notionalFloor": 400000, - "maintMarginRatio": 0.25, - "cum": 43920.0 - } - }, - { - "tier": 7.0, - "symbol": "\u5e01\u5b89\u4eba\u751f/USDT:USDT", - "currency": "USDT", - "minNotional": 1200000.0, - "maxNotional": 4000000.0, + "minNotional": 1000000.0, + "maxNotional": 3000000.0, "maintenanceMarginRate": 0.5, "maxLeverage": 1.0, "info": { - "bracket": 7, + "bracket": 6, "initialLeverage": 1, - "notionalCap": 4000000, - "notionalFloor": 1200000, + "notionalCap": 3000000, + "notionalFloor": 1000000, "maintMarginRatio": 0.5, - "cum": 343920.0 + "cum": 281994.0 } } ], From d460eef576417ce1c9756ee17cf402724d3a6485 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 30 Apr 2026 07:08:05 +0200 Subject: [PATCH 315/315] chore: bump version to 2026.4 --- freqtrade/__init__.py | 2 +- ft_client/freqtrade_client/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 7238eab9d..beadda2b4 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,6 +1,6 @@ """Freqtrade bot""" -__version__ = "2026.4-dev" +__version__ = "2026.4" if "dev" in __version__: from pathlib import Path diff --git a/ft_client/freqtrade_client/__init__.py b/ft_client/freqtrade_client/__init__.py index ba368c8e4..20f75327b 100644 --- a/ft_client/freqtrade_client/__init__.py +++ b/ft_client/freqtrade_client/__init__.py @@ -1,7 +1,7 @@ from freqtrade_client.ft_rest_client import FtRestClient -__version__ = "2026.4-dev" +__version__ = "2026.4" if "dev" in __version__: from pathlib import Path