diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 969d1cea2..5024a34fe 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -46,7 +46,7 @@ jobs:
uses: actions/cache@v4
with:
path: ~/.cache/pip
- key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip
+ key: pip-${{ matrix.python-version }}-ubuntu
- name: TA binary *nix
if: steps.cache.outputs.cache-hit != 'true'
@@ -167,7 +167,7 @@ jobs:
uses: actions/cache@v4
with:
path: ~/Library/Caches/pip
- key: ${{ matrix.os }}-${{ matrix.python-version }}-pip
+ key: pip-${{ matrix.os }}-${{ matrix.python-version }}
- name: TA binary *nix
if: steps.cache.outputs.cache-hit != 'true'
@@ -196,7 +196,7 @@ jobs:
rm /usr/local/bin/python3.11-config || true
rm /usr/local/bin/python3.12-config || true
- brew install hdf5 c-blosc libomp
+ brew install libomp
- name: Installation (python)
run: |
@@ -280,7 +280,7 @@ jobs:
uses: actions/cache@v4
with:
path: ~\AppData\Local\pip\Cache
- key: ${{ matrix.os }}-${{ matrix.python-version }}-pip
+ key: pip-${{ matrix.os }}-${{ matrix.python-version }}
- name: Installation
run: |
@@ -420,7 +420,7 @@ jobs:
uses: actions/cache@v4
with:
path: ~/.cache/pip
- key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip
+ key: pip-3.12-ubuntu
- name: TA binary *nix
if: steps.cache.outputs.cache-hit != 'true'
@@ -540,12 +540,12 @@ jobs:
- name: Publish to PyPI (Test)
- uses: pypa/gh-action-pypi-publish@v1.10.3
+ uses: pypa/gh-action-pypi-publish@v1.12.2
with:
repository-url: https://test.pypi.org/legacy/
- name: Publish to PyPI
- uses: pypa/gh-action-pypi-publish@v1.10.3
+ uses: pypa/gh-action-pypi-publish@v1.12.2
deploy-docker:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 67185b19c..e8665b76f 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -9,7 +9,7 @@ repos:
# stages: [push]
- repo: https://github.com/pre-commit/mirrors-mypy
- rev: "v1.12.1"
+ rev: "v1.13.0"
hooks:
- id: mypy
exclude: build_helpers
@@ -31,7 +31,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
- rev: 'v0.7.0'
+ rev: 'v0.7.3'
hooks:
- id: ruff
- id: ruff-format
diff --git a/build_helpers/pyarrow-17.0.0-cp311-cp311-linux_armv7l.whl b/build_helpers/pyarrow-18.0.0-cp311-cp311-linux_armv7l.whl
similarity index 62%
rename from build_helpers/pyarrow-17.0.0-cp311-cp311-linux_armv7l.whl
rename to build_helpers/pyarrow-18.0.0-cp311-cp311-linux_armv7l.whl
index 4fe505588..adb13b65f 100644
Binary files a/build_helpers/pyarrow-17.0.0-cp311-cp311-linux_armv7l.whl and b/build_helpers/pyarrow-18.0.0-cp311-cp311-linux_armv7l.whl differ
diff --git a/build_helpers/schema.json b/build_helpers/schema.json
index 6a73e75b0..e12b0bf0d 100644
--- a/build_helpers/schema.json
+++ b/build_helpers/schema.json
@@ -682,12 +682,18 @@
},
"exit_fill": {
"description": "Telegram setting for exit fill signals.",
- "type": "string",
- "enum": [
- "on",
- "off",
- "silent"
+ "type": [
+ "string",
+ "object"
],
+ "additionalProperties": {
+ "type": "string",
+ "enum": [
+ "on",
+ "off",
+ "silent"
+ ]
+ },
"default": "on"
},
"exit_cancel": {
@@ -1383,6 +1389,11 @@
"type": "string",
"default": "example"
},
+ "wait_for_training_iteration_on_reload": {
+ "description": "Wait for the next training iteration to complete after /reload or ctrl+c.",
+ "type": "boolean",
+ "default": true
+ },
"feature_parameters": {
"description": "The parameters used to engineer the feature set",
"type": "object",
diff --git a/docs/advanced-hyperopt.md b/docs/advanced-hyperopt.md
index e276bed94..480b20daf 100644
--- a/docs/advanced-hyperopt.md
+++ b/docs/advanced-hyperopt.md
@@ -37,8 +37,8 @@ class SuperDuperHyperOptLoss(IHyperOptLoss):
min_date: datetime,
max_date: datetime,
config: Config,
- processed: Dict[str, DataFrame],
- backtest_stats: Dict[str, Any],
+ processed: dict[str, DataFrame],
+ backtest_stats: dict[str, Any],
**kwargs,
) -> float:
"""
@@ -103,7 +103,7 @@ class MyAwesomeStrategy(IStrategy):
SKDecimal(0.01, 0.20, decimals=3, name='roi_p3'),
]
- def generate_roi_table(params: Dict) -> Dict[int, float]:
+ def generate_roi_table(params: Dict) -> dict[int, float]:
roi_table = {}
roi_table[0] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
diff --git a/docs/backtesting.md b/docs/backtesting.md
index 12a6c346e..f7034b3b2 100644
--- a/docs/backtesting.md
+++ b/docs/backtesting.md
@@ -10,12 +10,14 @@ To learn how to get data for the pairs and exchange you're interested in, head o
```
usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [-s NAME]
- [--strategy-path PATH] [-i TIMEFRAME]
- [--timerange TIMERANGE]
- [--data-format-ohlcv {json,jsongz,hdf5}]
+ [--strategy-path PATH]
+ [--recursive-strategy-search]
+ [--freqaimodel NAME] [--freqaimodel-path PATH]
+ [-i TIMEFRAME] [--timerange TIMERANGE]
+ [--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
[--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
- [-p PAIRS [PAIRS ...]] [--eps] [--dmmp]
+ [-p PAIRS [PAIRS ...]] [--eps]
[--enable-protections]
[--dry-run-wallet DRY_RUN_WALLET]
[--timeframe-detail TIMEFRAME_DETAIL]
@@ -24,8 +26,9 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[--export-filename PATH]
[--breakdown {day,week,month} [{day,week,month} ...]]
[--cache {none,day,week,month}]
+ [--freqai-backtest-live-models]
-optional arguments:
+options:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
@@ -48,10 +51,6 @@ optional arguments:
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
- --dmmp, --disable-max-market-positions
- Disable applying `max_open_trades` during backtest
- (same as setting `max_open_trades` to a very high
- number).
--enable-protections, --enableprotections
Enable protections for backtesting.Will slow
backtesting down by a considerable amount, but will
@@ -80,10 +79,13 @@ optional arguments:
--cache {none,day,week,month}
Load a cached backtest result no older than specified
age (default: day).
+ --freqai-backtest-live-models
+ Run backtest with ready models.
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
- --logfile FILE Log to the file specified. Special values are:
+ --logfile FILE, --log-file FILE
+ Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
@@ -92,7 +94,7 @@ Common arguments:
`userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
- -d PATH, --datadir PATH
+ -d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
@@ -102,6 +104,12 @@ Strategy arguments:
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
+ --recursive-strategy-search
+ Recursively search for a strategy in the strategies
+ folder.
+ --freqaimodel NAME Specify a custom freqaimodels.
+ --freqaimodel-path PATH
+ Specify additional lookup path for freqaimodels.
```
@@ -558,6 +566,7 @@ Since backtesting lacks some detailed information about what happens within a ca
- Stoploss
- ROI
- Trailing stoploss
+- Position reversals (futures only) happen if an entry signal in the other direction than the closing trade triggers at the candle the existing trade closes.
Taking these assumptions, backtesting tries to mirror real trading as closely as possible. However, backtesting will **never** replace running a strategy in dry-run mode.
Also, keep in mind that past results don't guarantee future success.
diff --git a/docs/exchanges.md b/docs/exchanges.md
index c4c9cf450..34bab4c22 100644
--- a/docs/exchanges.md
+++ b/docs/exchanges.md
@@ -252,6 +252,14 @@ OKX requires a passphrase for each api key, you will therefore need to add this
Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0).
The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value.
+Gate API keys require the following permissions on top of the market type you want to trade:
+
+* "Spot Trade" _or_ "Perpetual Futures" (Read and Write) (either select both, or the one matching the market you want to trade)
+* "Wallet" (read only)
+* "Account" (read only)
+
+Without these permissions, the bot will not start correctly and show errors like "permission missing".
+
## Bybit
Futures trading on bybit is currently supported for USDT markets, and will use isolated futures mode.
diff --git a/docs/freqai-configuration.md b/docs/freqai-configuration.md
index 5caa89a42..3f01ca81b 100644
--- a/docs/freqai-configuration.md
+++ b/docs/freqai-configuration.md
@@ -293,10 +293,10 @@ class MyCoolPyTorchClassifier(BasePyTorchClassifier):
super().__init__(**kwargs)
config = self.freqai_info.get("model_training_parameters", {})
self.learning_rate: float = config.get("learning_rate", 3e-4)
- self.model_kwargs: Dict[str, Any] = config.get("model_kwargs", {})
- self.trainer_kwargs: Dict[str, Any] = config.get("trainer_kwargs", {})
+ self.model_kwargs: dict[str, Any] = config.get("model_kwargs", {})
+ self.trainer_kwargs: dict[str, Any] = config.get("trainer_kwargs", {})
- def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
+ def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
:param data_dictionary: the dictionary holding all data for train, test,
@@ -359,10 +359,10 @@ class PyTorchMLPRegressor(BasePyTorchRegressor):
super().__init__(**kwargs)
config = self.freqai_info.get("model_training_parameters", {})
self.learning_rate: float = config.get("learning_rate", 3e-4)
- self.model_kwargs: Dict[str, Any] = config.get("model_kwargs", {})
- self.trainer_kwargs: Dict[str, Any] = config.get("trainer_kwargs", {})
+ self.model_kwargs: dict[str, Any] = config.get("model_kwargs", {})
+ self.trainer_kwargs: dict[str, Any] = config.get("trainer_kwargs", {})
- def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
+ def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
n_features = data_dictionary["train_features"].shape[-1]
model = PyTorchMLPModel(
input_dim=n_features,
diff --git a/docs/freqai-parameter-table.md b/docs/freqai-parameter-table.md
index 8a02faad2..3bb289313 100644
--- a/docs/freqai-parameter-table.md
+++ b/docs/freqai-parameter-table.md
@@ -22,6 +22,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
| `write_metrics_to_disk` | Collect train timings, inference timings and cpu usage in json file.
**Datatype:** Boolean.
Default: `False`
| `data_kitchen_thread_count` |
Designate the number of threads you want to use for data processing (outlier methods, normalization, etc.). This has no impact on the number of threads used for training. If user does not set it (default), FreqAI will use max number of threads - 2 (leaving 1 physical core available for Freqtrade bot and FreqUI)
**Datatype:** Positive integer.
| `activate_tensorboard` |
Indicate whether or not to activate tensorboard for the tensorboard enabled modules (currently Reinforcment Learning, XGBoost, Catboost, and PyTorch). Tensorboard needs Torch installed, which means you will need the torch/RL docker image or you need to answer "yes" to the install question about whether or not you wish to install Torch.
**Datatype:** Boolean.
Default: `True`.
+| `wait_for_training_iteration_on_reload` |
When using /reload or ctrl-c, wait for the current training iteration to finish before completing graceful shutdown. If set to `False`, FreqAI will break the current training iteration, allowing you to shutdown gracefully more quickly, but you will lose your current training iteration.
**Datatype:** Boolean.
Default: `True`.
### Feature parameters
diff --git a/docs/hyperopt.md b/docs/hyperopt.md
index 6788f681a..2b703ec6b 100644
--- a/docs/hyperopt.md
+++ b/docs/hyperopt.md
@@ -42,11 +42,11 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--recursive-strategy-search] [--freqaimodel NAME]
[--freqaimodel-path PATH] [-i TIMEFRAME]
[--timerange TIMERANGE]
- [--data-format-ohlcv {json,jsongz,hdf5}]
+ [--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
[--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[-p PAIRS [PAIRS ...]] [--hyperopt-path PATH]
- [--eps] [--dmmp] [--enable-protections]
+ [--eps] [--enable-protections]
[--dry-run-wallet DRY_RUN_WALLET]
[--timeframe-detail TIMEFRAME_DETAIL] [-e INT]
[--spaces {all,buy,sell,roi,stoploss,trailing,protection,trades,default} [{all,buy,sell,roi,stoploss,trailing,protection,trades,default} ...]]
@@ -55,15 +55,15 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--hyperopt-loss NAME] [--disable-param-export]
[--ignore-missing-spaces] [--analyze-per-epoch]
-optional arguments:
+options:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
- --data-format-ohlcv {json,jsongz,hdf5}
+ --data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded candle (OHLCV) data.
- (default: `json`).
+ (default: `feather`).
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.
@@ -80,10 +80,6 @@ optional arguments:
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
- --dmmp, --disable-max-market-positions
- Disable applying `max_open_trades` during backtest
- (same as setting `max_open_trades` to a very high
- number).
--enable-protections, --enableprotections
Enable protections for backtesting.Will slow
backtesting down by a considerable amount, but will
@@ -133,7 +129,8 @@ optional arguments:
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
- --logfile FILE Log to the file specified. Special values are:
+ --logfile FILE, --log-file FILE
+ Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
@@ -142,7 +139,7 @@ Common arguments:
`userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
- -d PATH, --datadir PATH
+ -d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
@@ -867,18 +864,15 @@ You can use the `--print-all` command line option if you would like to see all r
## Position stacking and disabling max market positions
-In some situations, you may need to run Hyperopt (and Backtesting) with the
-`--eps`/`--enable-position-staking` and `--dmmp`/`--disable-max-market-positions` arguments.
+In some situations, you may need to run Hyperopt (and Backtesting) with the `--eps`/`--enable-position-staking` argument, or you may need to set `max_open_trades` to a very high number to disable the limit on the number of open trades.
By default, hyperopt emulates the behavior of the Freqtrade Live Run/Dry Run, where only one
-open trade is allowed for every traded pair. The total number of trades open for all pairs
+open trade per pair is allowed. The total number of trades open for all pairs
is also limited by the `max_open_trades` setting. During Hyperopt/Backtesting this may lead to
-some potential trades to be hidden (or masked) by previously open trades.
+potential trades being hidden (or masked) by already open trades.
-The `--eps`/`--enable-position-stacking` argument allows emulation of buying the same pair multiple times,
-while `--dmmp`/`--disable-max-market-positions` disables applying `max_open_trades`
-during Hyperopt/Backtesting (which is equal to setting `max_open_trades` to a very high
-number).
+The `--eps`/`--enable-position-stacking` argument allows emulation of buying the same pair multiple times.
+Using `--max-open-trades` with a very high number will disable the limit on the number of open trades.
!!! Note
Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality.
@@ -923,7 +917,7 @@ After you run Hyperopt for the desired amount of epochs, you can later list all
Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected.
-To achieve same the results (number of trades, their durations, profit, etc.) as during Hyperopt, please use the same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
+To achieve same the results (number of trades, their durations, profit, etc.) as during Hyperopt, please use the same configuration and parameters (timerange, timeframe, ...) used for hyperopt for Backtesting.
### Why do my backtest results not match my hyperopt results?
Should results not match, check the following factors:
diff --git a/docs/includes/pairlists.md b/docs/includes/pairlists.md
index 8d79a7bc1..7e4659807 100644
--- a/docs/includes/pairlists.md
+++ b/docs/includes/pairlists.md
@@ -352,7 +352,7 @@ The optional `bearer_token` will be included in the requests Authorization Heade
#### MarketCapPairList
-`MarketCapPairList` employs sorting/filtering of pairs by their marketcap rank based of CoinGecko. It will only recognize coins up to the coin placed at rank 250. The returned pairlist will be sorted based of their marketcap ranks.
+`MarketCapPairList` employs sorting/filtering of pairs by their marketcap rank based of CoinGecko. The returned pairlist will be sorted based of their marketcap ranks.
```json
"pairlists": [
@@ -366,7 +366,8 @@ The optional `bearer_token` will be included in the requests Authorization Heade
]
```
-`number_assets` defines the maximum number of pairs returned by the pairlist. `max_rank` will determine the maximum rank used in creating/filtering the pairlist. It's expected that some coins within the top `max_rank` marketcap will not be included in the resulting pairlist since not all pairs will have active trading pairs in your preferred market/stake/exchange combination.
+`number_assets` defines the maximum number of pairs returned by the pairlist. `max_rank` will determine the maximum rank used in creating/filtering the pairlist. It's expected that some coins within the top `max_rank` marketcap will not be included in the resulting pairlist since not all pairs will have active trading pairs in your preferred market/stake/exchange combination.
+While using a `max_rank` bigger than 250 is supported, it's not recommended, as it'll cause multiple API calls to CoinGecko, which can lead to rate limit issues.
The `refresh_period` setting defines the interval (in seconds) at which the marketcap rank data will be refreshed. The default is 86,400 seconds (1 day). The pairlist cache (`refresh_period`) applies to both generating pairlists (when in the first position in the list) and filtering instances (when not in the first position in the list).
diff --git a/docs/installation.md b/docs/installation.md
index 02cbb7f3e..30deb07ba 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -67,6 +67,18 @@ OS Specific steps are listed first, the common section below is necessary for al
sudo apt install -y python3-pip python3-venv python3-dev python3-pandas git curl
```
+=== "MacOS"
+ #### Install necessary dependencies
+
+ Install [Homebrew](https://brew.sh/) if you don't have it already.
+
+ ```bash
+ # install packages
+ brew install gettext libomp
+ ```
+ !!! Note
+ The `setup.sh` script will install these dependencies for you - assuming brew is installed on your system.
+
=== "RaspberryPi/Raspbian"
The following assumes the latest [Raspbian Buster lite image](https://www.raspberrypi.org/downloads/raspbian/).
This image comes with python3.11 preinstalled, making it easy to get freqtrade up and running.
@@ -76,7 +88,7 @@ OS Specific steps are listed first, the common section below is necessary for al
```bash
sudo apt-get install python3-venv libatlas-base-dev cmake curl
- # Use pywheels.org to speed up installation
+ # Use piwheels.org to speed up installation
sudo echo "[global]\nextra-index-url=https://www.piwheels.org/simple" > tee /etc/pip.conf
git clone https://github.com/freqtrade/freqtrade.git
@@ -150,9 +162,7 @@ Each time you open a new terminal, you must run `source .venv/bin/activate` to a
source ./.venv/bin/activate
```
-### Congratulations
-
-[You are ready](#you-are-ready), and run the bot
+[You are now ready](#you-are-ready) to run the bot.
### Other options of /setup.sh script
@@ -220,7 +230,7 @@ cd ..
rm -rf ./ta-lib*
```
-#### Setup Python virtual environment (virtualenv)
+### Setup Python virtual environment (virtualenv)
You will run freqtrade in separated `virtual environment`
@@ -232,19 +242,18 @@ python3 -m venv .venv
source .venv/bin/activate
```
-#### Install python dependencies
+### Install python dependencies
```bash
python3 -m pip install --upgrade pip
python3 -m pip install -r requirements.txt
+# install freqtrade
python3 -m pip install -e .
```
-### Congratulations
+[You are now ready](#you-are-ready) to run the bot.
-[You are ready](#you-are-ready), and run the bot
-
-#### (Optional) Post-installation Tasks
+### (Optional) Post-installation Tasks
!!! Note
If you run the bot on a server, you should consider using [Docker](docker_quickstart.md) or a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout.
@@ -333,9 +342,7 @@ cd build_helpers
bash install_ta-lib.sh ${CONDA_PREFIX} nosudo
```
-### Congratulations
-
-[You are ready](#you-are-ready), and run the bot
+[You are now ready](#you-are-ready) to run the bot.
### Important shortcuts
diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt
index c757bf951..3ed50eaaf 100644
--- a/docs/requirements-docs.txt
+++ b/docs/requirements-docs.txt
@@ -1,7 +1,7 @@
markdown==3.7
mkdocs==1.6.1
-mkdocs-material==9.5.42
+mkdocs-material==9.5.44
mdx_truly_sane_lists==1.3
-pymdown-extensions==10.11.2
+pymdown-extensions==10.12
jinja2==3.1.4
mike==2.1.3
diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md
index ce1b9907c..715ebdde2 100644
--- a/docs/strategy-callbacks.md
+++ b/docs/strategy-callbacks.md
@@ -165,7 +165,8 @@ Called for open trade every iteration (roughly every 5 seconds) until a trade is
The usage of the custom stoploss method must be enabled by setting `use_custom_stoploss=True` on the strategy object.
-The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss (before this method is called for the first time for a trade), and is still mandatory.
+The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss (before this method is called for the first time for a trade), and is still mandatory.
+As custom stoploss acts as regular, changing stoploss, it will behave similar to `trailing_stop` - and trades exiting due to this will have the exit_reason of `"trailing_stop_loss"`.
The method must return a stoploss value (float / number) as a percentage of the current price.
E.g. If the `current_rate` is 200 USD, then returning `0.02` will set the stoploss price 2% lower, at 196 USD.
@@ -834,7 +835,7 @@ class DigDeeperStrategy(IStrategy):
current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float,
**kwargs
- ) -> Union[Optional[float], Tuple[Optional[float], Optional[str]]]:
+ ) -> Union[Optional[float], tuple[Optional[float], Optional[str]]]:
"""
Custom trade adjustment logic, returning the stake amount that a trade should be
increased or decreased.
@@ -890,7 +891,7 @@ class DigDeeperStrategy(IStrategy):
# Hope you have a deep wallet!
try:
# This returns first order stake size
- stake_amount = filled_entries[0].stake_amount
+ stake_amount = filled_entries[0].stake_amount_filled
# This then calculates current safety order size
stake_amount = stake_amount * (1 + (count_of_entries * 0.25))
return stake_amount, "1/3rd_increase"
@@ -975,7 +976,7 @@ class AwesomeStrategy(IStrategy):
pair == "BTC/USDT"
and entry_tag == "long_sma200"
and side == "long"
- and (current_time - timedelta(minutes=10)) > trade.open_date_utc
+ and (current_time - timedelta(minutes=10)) <= trade.open_date_utc
):
# just cancel the order if it has been filled more than half of the amount
if order.filled > order.remaining:
diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md
index 5e3c65759..ae797d326 100644
--- a/docs/strategy-customization.md
+++ b/docs/strategy-customization.md
@@ -4,7 +4,7 @@ This page explains how to customize your strategies, add new indicators and set
If you haven't already, please familiarize yourself with:
-- the [Freqtrade strategy 101](freqtrade-101.md), which provides a quick start to strategy development
+- the [Freqtrade strategy 101](strategy-101.md), which provides a quick start to strategy development
- the [Freqtrade bot basics](bot-basics.md), which provides overall info on how the bot operates
## Develop your own strategy
diff --git a/docs/strategy_analysis_example.md b/docs/strategy_analysis_example.md
index 9f37b2975..a1c74162e 100644
--- a/docs/strategy_analysis_example.md
+++ b/docs/strategy_analysis_example.md
@@ -215,7 +215,7 @@ trades.groupby("pair")["exit_reason"].value_counts()
```
## Analyze the loaded trades for trade parallelism
-This can be useful to find the best `max_open_trades` parameter, when used with backtesting in conjunction with `--disable-max-market-positions`.
+This can be useful to find the best `max_open_trades` parameter, when used with backtesting in conjunction with a very high `max_open_trades` setting.
`analyze_trade_parallelism()` returns a timeseries dataframe with an "open_trades" column, specifying the number of open trades for each candle.
diff --git a/docs/strategy_migration.md b/docs/strategy_migration.md
index b423eca6e..a9748c413 100644
--- a/docs/strategy_migration.md
+++ b/docs/strategy_migration.md
@@ -780,7 +780,7 @@ class MyCoolFreqaiModel(BaseRegressionModel):
def predict(
self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs
- ) -> Tuple[DataFrame, npt.NDArray[np.int_]]:
+ ) -> tuple[DataFrame, npt.NDArray[np.int_]]:
# ... your custom stuff
diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md
index 8fc8b0cfa..3fc76d58f 100644
--- a/docs/telegram-usage.md
+++ b/docs/telegram-usage.md
@@ -58,6 +58,7 @@ For the Freqtrade configuration, you can then use the full value (including `-`
```json
"chat_id": "-1001332619709"
```
+
!!! Warning "Using telegram groups"
When using telegram groups, you're giving every member of the telegram group access to your freqtrade bot and to all commands possible via telegram. Please make sure that you can trust everyone in the telegram group to avoid unpleasant surprises.
@@ -93,9 +94,12 @@ Example configuration showing the different settings:
"trailing_stop_loss": "on",
"stop_loss": "on",
"stoploss_on_exchange": "on",
- "custom_exit": "silent",
- "partial_exit": "on"
+ "custom_exit": "silent", // custom_exit without specifying an exit reason
+ "partial_exit": "on",
+ // "custom_exit_message": "silent", // Disable individual custom exit reasons
+ "*": "off" // Disable all other exit reasons
},
+ // "exit": "off", // Simplistic configuration to disable all exit messages
"exit_cancel": "on",
"exit_fill": "off",
"protection_trigger": "off",
@@ -108,16 +112,16 @@ Example configuration showing the different settings:
},
```
-`entry` notifications are sent when the order is placed, while `entry_fill` notifications are sent when the order is filled on the exchange.
-`exit` notifications are sent when the order is placed, while `exit_fill` notifications are sent when the order is filled on the exchange.
-`*_fill` notifications are off by default and must be explicitly enabled.
-`protection_trigger` notifications are sent when a protection triggers and `protection_trigger_global` notifications trigger when global protections are triggered.
-`strategy_msg` - Receive notifications from the strategy, sent via `self.dp.send_msg()` from the strategy [more details](strategy-customization.md#send-notification).
-`show_candle` - show candle values as part of entry/exit messages. Only possible values are `"ohlc"` or `"off"`.
-
-`balance_dust_level` will define what the `/balance` command takes as "dust" - Currencies with a balance below this will be shown.
-`allow_custom_messages` completely disable strategy messages.
-`reload` allows you to disable reload-buttons on selected messages.
+* `entry` notifications are sent when the order is placed, while `entry_fill` notifications are sent when the order is filled on the exchange.
+* `exit` notifications are sent when the order is placed, while `exit_fill` notifications are sent when the order is filled on the exchange.
+ Exit messages (`exit` and `exit_fill`) can be further controlled at individual exit reasons level, with the specific exit reason as the key. the default for all exit reasons is `on` - but can be configured via special `*` key - which will act as a wildcard for all exit reasons that are not explicitly defined.
+* `*_fill` notifications are off by default and must be explicitly enabled.
+* `protection_trigger` notifications are sent when a protection triggers and `protection_trigger_global` notifications trigger when global protections are triggered.
+* `strategy_msg` - Receive notifications from the strategy, sent via `self.dp.send_msg()` from the strategy [more details](strategy-customization.md#send-notification).
+* `show_candle` - show candle values as part of entry/exit messages. Only possible values are `"ohlc"` or `"off"`.
+* `balance_dust_level` will define what the `/balance` command takes as "dust" - Currencies with a balance below this will be shown.
+* `allow_custom_messages` completely disable strategy messages.
+* `reload` allows you to disable reload-buttons on selected messages.
## Create a custom keyboard (command shortcut buttons)
@@ -238,16 +242,16 @@ Once all positions are sold, run `/stop` to completely stop the bot.
For each open trade, the bot will send you the following message.
Enter Tag is configurable via Strategy.
-> **Trade ID:** `123` `(since 1 days ago)`
-> **Current Pair:** CVC/BTC
-> **Direction:** Long
-> **Leverage:** 1.0
-> **Amount:** `26.64180098`
-> **Enter Tag:** Awesome Long Signal
-> **Open Rate:** `0.00007489`
-> **Current Rate:** `0.00007489`
-> **Unrealized Profit:** `12.95%`
-> **Stoploss:** `0.00007389 (-0.02%)`
+> **Trade ID:** `123` `(since 1 days ago)`
+> **Current Pair:** CVC/BTC
+> **Direction:** Long
+> **Leverage:** 1.0
+> **Amount:** `26.64180098`
+> **Enter Tag:** Awesome Long Signal
+> **Open Rate:** `0.00007489`
+> **Current Rate:** `0.00007489`
+> **Unrealized Profit:** `12.95%`
+> **Stoploss:** `0.00007389 (-0.02%)`
### /status table
@@ -274,34 +278,34 @@ current max
Return a summary of your profit/loss and performance.
-> **ROI:** Close trades
-> ∙ `0.00485701 BTC (2.2%) (15.2 Σ%)`
-> ∙ `62.968 USD`
-> **ROI:** All trades
-> ∙ `0.00255280 BTC (1.5%) (6.43 Σ%)`
-> ∙ `33.095 EUR`
->
-> **Total Trade Count:** `138`
-> **Bot started:** `2022-07-11 18:40:44`
-> **First Trade opened:** `3 days ago`
-> **Latest Trade opened:** `2 minutes ago`
-> **Avg. Duration:** `2:33:45`
-> **Best Performing:** `PAY/BTC: 50.23%`
-> **Trading volume:** `0.5 BTC`
-> **Profit factor:** `1.04`
-> **Win / Loss:** `102 / 36`
-> **Winrate:** `73.91%`
-> **Expectancy (Ratio):** `4.87 (1.66)`
-> **Max Drawdown:** `9.23% (0.01255 BTC)`
+> **ROI:** Close trades
+> ∙ `0.00485701 BTC (2.2%) (15.2 Σ%)`
+> ∙ `62.968 USD`
+> **ROI:** All trades
+> ∙ `0.00255280 BTC (1.5%) (6.43 Σ%)`
+> ∙ `33.095 EUR`
+>
+> **Total Trade Count:** `138`
+> **Bot started:** `2022-07-11 18:40:44`
+> **First Trade opened:** `3 days ago`
+> **Latest Trade opened:** `2 minutes ago`
+> **Avg. Duration:** `2:33:45`
+> **Best Performing:** `PAY/BTC: 50.23%`
+> **Trading volume:** `0.5 BTC`
+> **Profit factor:** `1.04`
+> **Win / Loss:** `102 / 36`
+> **Winrate:** `73.91%`
+> **Expectancy (Ratio):** `4.87 (1.66)`
+> **Max Drawdown:** `9.23% (0.01255 BTC)`
-The relative profit of `1.2%` is the average profit per trade.
-The relative profit of `15.2 Σ%` is be based on the starting capital - so in this case, the starting capital was `0.00485701 * 1.152 = 0.00738 BTC`.
-Starting capital is either taken from the `available_capital` setting, or calculated by using current wallet size - profits.
-Profit Factor is calculated as gross profits / gross losses - and should serve as an overall metric for the strategy.
-Expectancy corresponds to the average return per currency unit at risk, i.e. the winrate and the risk-reward ratio (the average gain of winning trades compared to the average loss of losing trades).
-Expectancy Ratio is expected profit or loss of a subsequent trade based on the performance of all past trades.
-Max drawdown corresponds to the backtesting metric `Absolute Drawdown (Account)` - calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.
-Bot started date will refer to the date the bot was first started. For older bots, this will default to the first trade's open date.
+The relative profit of `1.2%` is the average profit per trade.
+The relative profit of `15.2 Σ%` is be based on the starting capital - so in this case, the starting capital was `0.00485701 * 1.152 = 0.00738 BTC`.
+**Starting capital(**) is either taken from the `available_capital` setting, or calculated by using current wallet size - profits.
+**Profit Factor** is calculated as gross profits / gross losses - and should serve as an overall metric for the strategy.
+**Expectancy** corresponds to the average return per currency unit at risk, i.e. the winrate and the risk-reward ratio (the average gain of winning trades compared to the average loss of losing trades).
+**Expectancy Ratio** is expected profit or loss of a subsequent trade based on the performance of all past trades.
+**Max drawdown** corresponds to the backtesting metric `Absolute Drawdown (Account)` - calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.
+**Bot started date** will refer to the date the bot was first started. For older bots, this will default to the first trade's open date.
### /forceexit
@@ -329,33 +333,34 @@ Note that for this to work, `force_entry_enable` needs to be set to true.
### /performance
Return the performance of each crypto-currency the bot has sold.
-> Performance:
-> 1. `RCN/BTC 0.003 BTC (57.77%) (1)`
-> 2. `PAY/BTC 0.0012 BTC (56.91%) (1)`
-> 3. `VIB/BTC 0.0011 BTC (47.07%) (1)`
-> 4. `SALT/BTC 0.0010 BTC (30.24%) (1)`
-> 5. `STORJ/BTC 0.0009 BTC (27.24%) (1)`
-> ...
+> Performance:
+> 1. `RCN/BTC 0.003 BTC (57.77%) (1)`
+> 2. `PAY/BTC 0.0012 BTC (56.91%) (1)`
+> 3. `VIB/BTC 0.0011 BTC (47.07%) (1)`
+> 4. `SALT/BTC 0.0010 BTC (30.24%) (1)`
+> 5. `STORJ/BTC 0.0009 BTC (27.24%) (1)`
+> ...
### /balance
Return the balance of all crypto-currency your have on the exchange.
-> **Currency:** BTC
-> **Available:** 3.05890234
-> **Balance:** 3.05890234
-> **Pending:** 0.0
-
-> **Currency:** CVC
-> **Available:** 86.64180098
-> **Balance:** 86.64180098
-> **Pending:** 0.0
+> **Currency:** BTC
+> **Available:** 3.05890234
+> **Balance:** 3.05890234
+> **Pending:** 0.0
+>
+> **Currency:** CVC
+> **Available:** 86.64180098
+> **Balance:** 86.64180098
+> **Pending:** 0.0
### /daily
Per default `/daily` will return the 7 last days. The example below if for `/daily 3`:
> **Daily Profit over the last 3 days:**
+
```
Day (count) USDT USD Profit %
-------------- ------------ ---------- ----------
@@ -370,6 +375,7 @@ Per default `/weekly` will return the 8 last weeks, including the current week.
from Monday. The example below if for `/weekly 3`:
> **Weekly Profit over the last 3 weeks (starting from Monday):**
+
```
Monday (count) Profit BTC Profit USD Profit %
------------- -------------- ------------ ----------
@@ -396,18 +402,18 @@ Month (count) Profit BTC Profit USD Profit %
Shows the current whitelist
-> Using whitelist `StaticPairList` with 22 pairs
+> Using whitelist `StaticPairList` with 22 pairs
> `IOTA/BTC, NEO/BTC, TRX/BTC, VET/BTC, ADA/BTC, ETC/BTC, NCASH/BTC, DASH/BTC, XRP/BTC, XVG/BTC, EOS/BTC, LTC/BTC, OMG/BTC, BTG/BTC, LSK/BTC, ZEC/BTC, HOT/BTC, IOTX/BTC, XMR/BTC, AST/BTC, XLM/BTC, NANO/BTC`
### /blacklist [pair]
Shows the current blacklist.
If Pair is set, then this pair will be added to the pairlist.
-Also supports multiple pairs, separated by a space.
+Also supports multiple pairs, separated by a space.
Use `/reload_config` to reset the blacklist.
-> Using blacklist `StaticPairList` with 2 pairs
->`DODGE/BTC`, `HOT/BTC`.
+> Using blacklist `StaticPairList` with 2 pairs
+>`DODGE/BTC`, `HOT/BTC`.
### /edge
diff --git a/docs/trade-object.md b/docs/trade-object.md
index 7434b826d..8a6e3b1a7 100644
--- a/docs/trade-object.md
+++ b/docs/trade-object.md
@@ -143,6 +143,7 @@ Most properties here can be None as they are dependent on the exchange response.
| `remaining` | float | Remaining amount |
| `cost` | float | Cost of the order - usually average * filled (*Exchange dependent on futures, may contain the cost with or without leverage and may be in contracts.*) |
| `stake_amount` | float | Stake amount used for this order. *Added in 2023.7.* |
+| `stake_amount_filled` | float | Filled Stake amount used for this order. *Added in 2024.11.* |
| `order_date` | datetime | Order creation date **use `order_date_utc` instead** |
| `order_date_utc` | datetime | Order creation date (in UTC) |
| `order_fill_date` | datetime | Order fill date **use `order_fill_utc` instead** |
diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py
index 08667e827..3f7ecaa22 100644
--- a/freqtrade/__init__.py
+++ b/freqtrade/__init__.py
@@ -1,6 +1,6 @@
"""Freqtrade bot"""
-__version__ = "2024.10-dev"
+__version__ = "2024.11-dev"
if "dev" in __version__:
from pathlib import Path
diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py
index 0bb572ebc..a56185471 100755
--- a/freqtrade/commands/arguments.py
+++ b/freqtrade/commands/arguments.py
@@ -5,7 +5,7 @@ This module contains the argument manager class
from argparse import ArgumentParser, Namespace, _ArgumentGroup
from functools import partial
from pathlib import Path
-from typing import Any, Optional, Union
+from typing import Any
from freqtrade.commands.cli_options import AVAILABLE_CLI_OPTIONS
from freqtrade.constants import DEFAULT_CONFIG
@@ -37,7 +37,6 @@ ARGS_COMMON_OPTIMIZE = [
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + [
"position_stacking",
- "use_max_market_positions",
"enable_protections",
"dry_run_wallet",
"timeframe_detail",
@@ -53,7 +52,6 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + [
"hyperopt",
"hyperopt_path",
"position_stacking",
- "use_max_market_positions",
"enable_protections",
"dry_run_wallet",
"timeframe_detail",
@@ -117,7 +115,7 @@ ARGS_CREATE_USERDIR = ["user_data_dir", "reset"]
ARGS_BUILD_CONFIG = ["config"]
ARGS_SHOW_CONFIG = ["user_data_dir", "config", "show_sensitive"]
-ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
+ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "strategy_path", "template"]
ARGS_CONVERT_DATA_TRADES = ["pairs", "format_from_trades", "format_to", "erase", "exchange"]
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase", "exchange"]
@@ -242,8 +240,7 @@ ARGS_STRATEGY_UPDATER = ["strategy_list", "strategy_path", "recursive_strategy_s
ARGS_LOOKAHEAD_ANALYSIS = [
a
for a in ARGS_BACKTEST
- if a
- not in ("position_stacking", "use_max_market_positions", "backtest_cache", "backtest_breakdown")
+ if a not in ("position_stacking", "backtest_cache", "backtest_breakdown")
] + ["minimum_trade_amount", "targeted_trade_amount", "lookahead_analysis_exportfilename"]
ARGS_RECURSIVE_ANALYSIS = ["timeframe", "timerange", "dataformat_ohlcv", "pairs", "startup_candle"]
@@ -278,9 +275,9 @@ class Arguments:
Arguments Class. Manage the arguments received by the cli
"""
- def __init__(self, args: Optional[list[str]]) -> None:
+ def __init__(self, args: list[str] | None) -> None:
self.args = args
- self._parsed_arg: Optional[Namespace] = None
+ self._parsed_arg: Namespace | None = None
def get_parsed_arg(self) -> dict[str, Any]:
"""
@@ -322,9 +319,7 @@ class Arguments:
return parsed_arg
- def _build_args(
- self, optionlist: list[str], parser: Union[ArgumentParser, _ArgumentGroup]
- ) -> None:
+ def _build_args(self, optionlist: list[str], parser: ArgumentParser | _ArgumentGroup) -> None:
for val in optionlist:
opt = AVAILABLE_CLI_OPTIONS[val]
parser.add_argument(*opt.cli, dest=val, **opt.kwargs)
diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py
index d279569c5..6e90a521f 100755
--- a/freqtrade/commands/cli_options.py
+++ b/freqtrade/commands/cli_options.py
@@ -168,14 +168,6 @@ AVAILABLE_CLI_OPTIONS = {
action="store_true",
default=False,
),
- "use_max_market_positions": Arg(
- "--dmmp",
- "--disable-max-market-positions",
- help="Disable applying `max_open_trades` during backtest "
- "(same as setting `max_open_trades` to a very high number).",
- action="store_false",
- default=True,
- ),
"backtest_show_pair_list": Arg(
"--show-pair-list",
help="Show backtesting pairlist sorted by profit.",
diff --git a/freqtrade/commands/deploy_commands.py b/freqtrade/commands/deploy_commands.py
index 0d188e514..acd975b9b 100644
--- a/freqtrade/commands/deploy_commands.py
+++ b/freqtrade/commands/deploy_commands.py
@@ -86,7 +86,14 @@ def start_new_strategy(args: dict[str, Any]) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
if "strategy" in args and args["strategy"]:
- new_path = config["user_data_dir"] / USERPATH_STRATEGIES / (args["strategy"] + ".py")
+ if "strategy_path" in args and args["strategy_path"]:
+ strategy_dir = Path(args["strategy_path"])
+ else:
+ strategy_dir = config["user_data_dir"] / USERPATH_STRATEGIES
+ if not strategy_dir.is_dir():
+ logger.info(f"Creating strategy directory {strategy_dir}")
+ strategy_dir.mkdir(parents=True)
+ new_path = strategy_dir / (args["strategy"] + ".py")
if new_path.exists():
raise OperationalException(
diff --git a/freqtrade/commands/deploy_ui.py b/freqtrade/commands/deploy_ui.py
index 283834b12..a02315bfc 100644
--- a/freqtrade/commands/deploy_ui.py
+++ b/freqtrade/commands/deploy_ui.py
@@ -1,6 +1,5 @@
import logging
from pathlib import Path
-from typing import Optional
import requests
@@ -24,7 +23,7 @@ def clean_ui_subdir(directory: Path):
p.rmdir()
-def read_ui_version(dest_folder: Path) -> Optional[str]:
+def read_ui_version(dest_folder: Path) -> str | None:
file = dest_folder / ".uiversion"
if not file.is_file():
return None
@@ -52,7 +51,7 @@ def download_and_install_ui(dest_folder: Path, dl_url: str, version: str):
f.write(version)
-def get_ui_download_url(version: Optional[str] = None) -> tuple[str, str]:
+def get_ui_download_url(version: str | None = None) -> tuple[str, str]:
base_url = "https://api.github.com/repos/freqtrade/frequi/"
# Get base UI Repo path
diff --git a/freqtrade/commands/list_commands.py b/freqtrade/commands/list_commands.py
index 0279f82ee..c8d476717 100644
--- a/freqtrade/commands/list_commands.py
+++ b/freqtrade/commands/list_commands.py
@@ -1,7 +1,7 @@
import csv
import logging
import sys
-from typing import Any, Union
+from typing import Any
from freqtrade.enums import RunMode
from freqtrade.exceptions import ConfigurationError, OperationalException
@@ -87,7 +87,7 @@ def _print_objs_tabular(objs: list, print_colorized: bool) -> None:
from rich.text import Text
names = [s["name"] for s in objs]
- objs_to_print: list[dict[str, Union[Text, str]]] = [
+ objs_to_print: list[dict[str, Text | str]] = [
{
"name": Text(s["name"] if s["name"] else "--"),
"location": s["location_rel"],
diff --git a/freqtrade/configuration/config_schema.py b/freqtrade/configuration/config_schema.py
index 9eb9c0477..30f1f6f28 100644
--- a/freqtrade/configuration/config_schema.py
+++ b/freqtrade/configuration/config_schema.py
@@ -517,8 +517,11 @@ CONF_SCHEMA = {
},
"exit_fill": {
"description": "Telegram setting for exit fill signals.",
- "type": "string",
- "enum": TELEGRAM_SETTING_OPTIONS,
+ "type": ["string", "object"],
+ "additionalProperties": {
+ "type": "string",
+ "enum": TELEGRAM_SETTING_OPTIONS,
+ },
"default": "on",
},
"exit_cancel": {
@@ -995,6 +998,13 @@ CONF_SCHEMA = {
"type": "string",
"default": "example",
},
+ "wait_for_training_iteration_on_reload": {
+ "description": (
+ "Wait for the next training iteration to complete after /reload or ctrl+c."
+ ),
+ "type": "boolean",
+ "default": True,
+ },
"feature_parameters": {
"description": "The parameters used to engineer the feature set",
"type": "object",
diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py
index c9ab86737..796296a7b 100644
--- a/freqtrade/configuration/configuration.py
+++ b/freqtrade/configuration/configuration.py
@@ -5,9 +5,10 @@ This module contains the configuration class
import ast
import logging
import warnings
+from collections.abc import Callable
from copy import deepcopy
from pathlib import Path
-from typing import Any, Callable, Optional
+from typing import Any
from freqtrade import constants
from freqtrade.configuration.deprecated_settings import process_temporary_deprecated_settings
@@ -37,9 +38,9 @@ class Configuration:
Reuse this class for the bot, backtesting, hyperopt and every script that required configuration
"""
- def __init__(self, args: dict[str, Any], runmode: Optional[RunMode] = None) -> None:
+ def __init__(self, args: dict[str, Any], runmode: RunMode | None = None) -> None:
self.args = args
- self.config: Optional[Config] = None
+ self.config: Config | None = None
self.runmode = runmode
def get_config(self) -> Config:
@@ -241,11 +242,7 @@ class Configuration:
logstring="Parameter --enable-protections detected, enabling Protections. ...",
)
- if "use_max_market_positions" in self.args and not self.args["use_max_market_positions"]:
- config.update({"use_max_market_positions": False})
- logger.info("Parameter --disable-max-market-positions detected ...")
- logger.info("max_open_trades set to unlimited ...")
- elif "max_open_trades" in self.args and self.args["max_open_trades"]:
+ if "max_open_trades" in self.args and self.args["max_open_trades"]:
config.update({"max_open_trades": self.args["max_open_trades"]})
logger.info(
"Parameter --max-open-trades detected, overriding max_open_trades to: %s ...",
@@ -455,8 +452,8 @@ class Configuration:
config: Config,
argname: str,
logstring: str,
- logfun: Optional[Callable] = None,
- deprecated_msg: Optional[str] = None,
+ logfun: Callable | None = None,
+ deprecated_msg: str | None = None,
) -> None:
"""
:param config: Configuration dictionary
diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py
index c4d78e588..202c317de 100644
--- a/freqtrade/configuration/deprecated_settings.py
+++ b/freqtrade/configuration/deprecated_settings.py
@@ -3,7 +3,6 @@ Functions to handle deprecated settings
"""
import logging
-from typing import Optional
from freqtrade.constants import Config
from freqtrade.exceptions import ConfigurationError, OperationalException
@@ -14,9 +13,9 @@ logger = logging.getLogger(__name__)
def check_conflicting_settings(
config: Config,
- section_old: Optional[str],
+ section_old: str | None,
name_old: str,
- section_new: Optional[str],
+ section_new: str | None,
name_new: str,
) -> None:
section_new_config = config.get(section_new, {}) if section_new else config
@@ -34,7 +33,7 @@ def check_conflicting_settings(
def process_removed_setting(
- config: Config, section1: str, name1: str, section2: Optional[str], name2: str
+ config: Config, section1: str, name1: str, section2: str | None, name2: str
) -> None:
"""
:param section1: Removed section
@@ -54,9 +53,9 @@ def process_removed_setting(
def process_deprecated_setting(
config: Config,
- section_old: Optional[str],
+ section_old: str | None,
name_old: str,
- section_new: Optional[str],
+ section_new: str | None,
name_new: str,
) -> None:
check_conflicting_settings(config, section_old, name_old, section_new, name_new)
diff --git a/freqtrade/configuration/directory_operations.py b/freqtrade/configuration/directory_operations.py
index 448cf1acd..4cbd444a3 100644
--- a/freqtrade/configuration/directory_operations.py
+++ b/freqtrade/configuration/directory_operations.py
@@ -1,7 +1,6 @@
import logging
import shutil
from pathlib import Path
-from typing import Optional
from freqtrade.configuration.detect_environment import running_in_docker
from freqtrade.constants import (
@@ -18,7 +17,7 @@ from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__)
-def create_datadir(config: Config, datadir: Optional[str] = None) -> Path:
+def create_datadir(config: Config, datadir: str | None = None) -> Path:
folder = Path(datadir) if datadir else Path(f"{config['user_data_dir']}/data")
if not datadir:
# set datadir
diff --git a/freqtrade/configuration/load_config.py b/freqtrade/configuration/load_config.py
index 6ff451246..e18707a99 100644
--- a/freqtrade/configuration/load_config.py
+++ b/freqtrade/configuration/load_config.py
@@ -7,7 +7,7 @@ import re
import sys
from copy import deepcopy
from pathlib import Path
-from typing import Any, Optional
+from typing import Any
import rapidjson
@@ -78,7 +78,7 @@ def load_config_file(path: str) -> dict[str, Any]:
def load_from_files(
- files: list[str], base_path: Optional[Path] = None, level: int = 0
+ files: list[str], base_path: Path | None = None, level: int = 0
) -> dict[str, Any]:
"""
Recursively load configuration files if specified.
diff --git a/freqtrade/configuration/timerange.py b/freqtrade/configuration/timerange.py
index 6449086fa..8afee82d1 100644
--- a/freqtrade/configuration/timerange.py
+++ b/freqtrade/configuration/timerange.py
@@ -5,7 +5,6 @@ This module contains the argument manager class
import logging
import re
from datetime import datetime, timezone
-from typing import Optional
from typing_extensions import Self
@@ -25,24 +24,24 @@ class TimeRange:
def __init__(
self,
- starttype: Optional[str] = None,
- stoptype: Optional[str] = None,
+ starttype: str | None = None,
+ stoptype: str | None = None,
startts: int = 0,
stopts: int = 0,
):
- self.starttype: Optional[str] = starttype
- self.stoptype: Optional[str] = stoptype
+ self.starttype: str | None = starttype
+ self.stoptype: str | None = stoptype
self.startts: int = startts
self.stopts: int = stopts
@property
- def startdt(self) -> Optional[datetime]:
+ def startdt(self) -> datetime | None:
if self.startts:
return datetime.fromtimestamp(self.startts, tz=timezone.utc)
return None
@property
- def stopdt(self) -> Optional[datetime]:
+ def stopdt(self) -> datetime | None:
if self.stopts:
return datetime.fromtimestamp(self.stopts, tz=timezone.utc)
return None
@@ -120,7 +119,7 @@ class TimeRange:
self.starttype = "date"
@classmethod
- def parse_timerange(cls, text: Optional[str]) -> Self:
+ def parse_timerange(cls, text: str | None) -> Self:
"""
Parse the value of the argument --timerange to determine what is the range desired
:param text: value from --timerange
diff --git a/freqtrade/constants.py b/freqtrade/constants.py
index 9acb3bdc0..020acf333 100644
--- a/freqtrade/constants.py
+++ b/freqtrade/constants.py
@@ -4,7 +4,7 @@
bot constants
"""
-from typing import Any, Literal, Optional
+from typing import Any, Literal
from freqtrade.enums import CandleType, PriceType
@@ -38,6 +38,7 @@ HYPEROPT_LOSS_BUILTIN = [
"MaxDrawDownHyperOptLoss",
"MaxDrawDownRelativeHyperOptLoss",
"ProfitDrawDownHyperOptLoss",
+ "MultiMetricHyperOptLoss",
]
AVAILABLE_PAIRLISTS = [
"StaticPairList",
@@ -193,7 +194,7 @@ ListPairsWithTimeframes = list[PairWithTimeframe]
# Type for trades list
TradeList = list[list]
# ticks, pair, timeframe, CandleType
-TickWithTimeframe = tuple[str, str, CandleType, Optional[int], Optional[int]]
+TickWithTimeframe = tuple[str, str, CandleType, int | None, int | None]
ListTicksWithTimeframes = list[TickWithTimeframe]
LongShort = Literal["long", "short"]
diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py
index 7e4d02f75..a9d8648d2 100644
--- a/freqtrade/data/btanalysis.py
+++ b/freqtrade/data/btanalysis.py
@@ -6,7 +6,7 @@ import logging
from copy import copy
from datetime import datetime, timezone
from pathlib import Path
-from typing import Any, Literal, Optional, Union
+from typing import Any, Literal
import numpy as np
import pandas as pd
@@ -53,7 +53,7 @@ BT_DATA_COLUMNS = [
]
-def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str:
+def get_latest_optimize_filename(directory: Path | str, variant: str) -> str:
"""
Get latest backtest export based on '.last_result.json'.
:param directory: Directory to search for last result
@@ -84,7 +84,7 @@ def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> s
return data[f"latest_{variant}"]
-def get_latest_backtest_filename(directory: Union[Path, str]) -> str:
+def get_latest_backtest_filename(directory: Path | str) -> str:
"""
Get latest backtest export based on '.last_result.json'.
:param directory: Directory to search for last result
@@ -97,7 +97,7 @@ def get_latest_backtest_filename(directory: Union[Path, str]) -> str:
return get_latest_optimize_filename(directory, "backtest")
-def get_latest_hyperopt_filename(directory: Union[Path, str]) -> str:
+def get_latest_hyperopt_filename(directory: Path | str) -> str:
"""
Get latest hyperopt export based on '.last_result.json'.
:param directory: Directory to search for last result
@@ -114,9 +114,7 @@ def get_latest_hyperopt_filename(directory: Union[Path, str]) -> str:
return "hyperopt_results.pickle"
-def get_latest_hyperopt_file(
- directory: Union[Path, str], predef_filename: Optional[str] = None
-) -> Path:
+def get_latest_hyperopt_file(directory: Path | str, predef_filename: str | None = None) -> Path:
"""
Get latest hyperopt export based on '.last_result.json'.
:param directory: Directory to search for last result
@@ -137,7 +135,7 @@ def get_latest_hyperopt_file(
return directory / get_latest_hyperopt_filename(directory)
-def load_backtest_metadata(filename: Union[Path, str]) -> dict[str, Any]:
+def load_backtest_metadata(filename: Path | str) -> dict[str, Any]:
"""
Read metadata dictionary from backtest results file without reading and deserializing entire
file.
@@ -154,7 +152,7 @@ def load_backtest_metadata(filename: Union[Path, str]) -> dict[str, Any]:
raise OperationalException("Unexpected error while loading backtest metadata.") from e
-def load_backtest_stats(filename: Union[Path, str]) -> BacktestResultType:
+def load_backtest_stats(filename: Path | str) -> BacktestResultType:
"""
Load backtest statistics file.
:param filename: pathlib.Path object, or string pointing to the file.
@@ -276,7 +274,7 @@ def get_backtest_market_change(filename: Path, include_ts: bool = True) -> pd.Da
def find_existing_backtest_stats(
- dirname: Union[Path, str], run_ids: dict[str, str], min_backtest_date: Optional[datetime] = None
+ dirname: Path | str, run_ids: dict[str, str], min_backtest_date: datetime | None = None
) -> dict[str, Any]:
"""
Find existing backtest stats that match specified run IDs and load them.
@@ -345,7 +343,7 @@ def _load_backtest_data_df_compatibility(df: pd.DataFrame) -> pd.DataFrame:
return df
-def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = None) -> pd.DataFrame:
+def load_backtest_data(filename: Path | str, strategy: str | None = None) -> pd.DataFrame:
"""
Load backtest data file.
:param filename: pathlib.Path object, or string pointing to a file or directory
@@ -439,7 +437,7 @@ def evaluate_result_multi(
return df_final[df_final["open_trades"] > max_open_trades]
-def trade_list_to_dataframe(trades: Union[list[Trade], list[LocalTrade]]) -> pd.DataFrame:
+def trade_list_to_dataframe(trades: list[Trade] | list[LocalTrade]) -> pd.DataFrame:
"""
Convert list of Trade objects to pandas Dataframe
:param trades: List of trade objects
@@ -453,7 +451,7 @@ def trade_list_to_dataframe(trades: Union[list[Trade], list[LocalTrade]]) -> pd.
return df
-def load_trades_from_db(db_url: str, strategy: Optional[str] = None) -> pd.DataFrame:
+def load_trades_from_db(db_url: str, strategy: str | None = None) -> pd.DataFrame:
"""
Load trades from a DB (using dburl)
:param db_url: Sqlite url (default format sqlite:///tradesv3.dry-run.sqlite)
@@ -476,7 +474,7 @@ def load_trades(
db_url: str,
exportfilename: Path,
no_trades: bool = False,
- strategy: Optional[str] = None,
+ strategy: str | None = None,
) -> pd.DataFrame:
"""
Based on configuration option 'trade_source':
diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py
index 491728695..7e4fc36d4 100644
--- a/freqtrade/data/dataprovider.py
+++ b/freqtrade/data/dataprovider.py
@@ -8,7 +8,7 @@ Common Interface for bot and strategy to access data.
import logging
from collections import deque
from datetime import datetime, timezone
-from typing import Any, Optional
+from typing import Any
from pandas import DataFrame, Timedelta, Timestamp, to_timedelta
@@ -40,17 +40,17 @@ class DataProvider:
def __init__(
self,
config: Config,
- exchange: Optional[Exchange],
+ exchange: Exchange | None,
pairlists=None,
- rpc: Optional[RPCManager] = None,
+ rpc: RPCManager | None = None,
) -> None:
self._config = config
self._exchange = exchange
self._pairlists = pairlists
self.__rpc = rpc
self.__cached_pairs: dict[PairWithTimeframe, tuple[DataFrame, datetime]] = {}
- self.__slice_index: Optional[int] = None
- self.__slice_date: Optional[datetime] = None
+ self.__slice_index: int | None = None
+ self.__slice_date: datetime | None = None
self.__cached_pairs_backtesting: dict[PairWithTimeframe, DataFrame] = {}
self.__producer_pairs_df: dict[
@@ -255,8 +255,8 @@ class DataProvider:
def get_producer_df(
self,
pair: str,
- timeframe: Optional[str] = None,
- candle_type: Optional[CandleType] = None,
+ timeframe: str | None = None,
+ candle_type: CandleType | None = None,
producer_name: str = "default",
) -> tuple[DataFrame, datetime]:
"""
@@ -349,7 +349,7 @@ class DataProvider:
return total_candles
def get_pair_dataframe(
- self, pair: str, timeframe: Optional[str] = None, candle_type: str = ""
+ self, pair: str, timeframe: str | None = None, candle_type: str = ""
) -> DataFrame:
"""
Return pair candle (OHLCV) data, either live or cached historical -- depending
@@ -437,7 +437,7 @@ class DataProvider:
def refresh(
self,
pairlist: ListPairsWithTimeframes,
- helping_pairs: Optional[ListPairsWithTimeframes] = None,
+ helping_pairs: ListPairsWithTimeframes | None = None,
) -> None:
"""
Refresh data, called with each cycle
@@ -471,7 +471,7 @@ class DataProvider:
return list(self._exchange._klines.keys())
def ohlcv(
- self, pair: str, timeframe: Optional[str] = None, copy: bool = True, candle_type: str = ""
+ self, pair: str, timeframe: str | None = None, copy: bool = True, candle_type: str = ""
) -> DataFrame:
"""
Get candle (OHLCV) data for the given pair as DataFrame
@@ -497,7 +497,7 @@ class DataProvider:
return DataFrame()
def trades(
- self, pair: str, timeframe: Optional[str] = None, copy: bool = True, candle_type: str = ""
+ self, pair: str, timeframe: str | None = None, copy: bool = True, candle_type: str = ""
) -> DataFrame:
"""
Get candle (TRADES) data for the given pair as DataFrame
@@ -529,7 +529,7 @@ class DataProvider:
)
return trades_df
- def market(self, pair: str) -> Optional[dict[str, Any]]:
+ def market(self, pair: str) -> dict[str, Any] | None:
"""
Return market data for the pair
:param pair: Pair to get the data for
diff --git a/freqtrade/data/history/datahandlers/featherdatahandler.py b/freqtrade/data/history/datahandlers/featherdatahandler.py
index 8b1acb09c..408ffddb7 100644
--- a/freqtrade/data/history/datahandlers/featherdatahandler.py
+++ b/freqtrade/data/history/datahandlers/featherdatahandler.py
@@ -1,5 +1,4 @@
import logging
-from typing import Optional
from pandas import DataFrame, read_feather, to_datetime
@@ -37,7 +36,7 @@ class FeatherDataHandler(IDataHandler):
)
def _ohlcv_load(
- self, pair: str, timeframe: str, timerange: Optional[TimeRange], candle_type: CandleType
+ self, pair: str, timeframe: str, timerange: TimeRange | None, candle_type: CandleType
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
@@ -108,7 +107,7 @@ class FeatherDataHandler(IDataHandler):
raise NotImplementedError()
def _trades_load(
- self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
+ self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> DataFrame:
"""
Load a pair from file, either .json.gz or .json
diff --git a/freqtrade/data/history/datahandlers/hdf5datahandler.py b/freqtrade/data/history/datahandlers/hdf5datahandler.py
index 99d0a98a6..b0f525b87 100644
--- a/freqtrade/data/history/datahandlers/hdf5datahandler.py
+++ b/freqtrade/data/history/datahandlers/hdf5datahandler.py
@@ -1,5 +1,4 @@
import logging
-from typing import Optional
import numpy as np
import pandas as pd
@@ -45,7 +44,7 @@ class HDF5DataHandler(IDataHandler):
)
def _ohlcv_load(
- self, pair: str, timeframe: str, timerange: Optional[TimeRange], candle_type: CandleType
+ self, pair: str, timeframe: str, timerange: TimeRange | None, candle_type: CandleType
) -> pd.DataFrame:
"""
Internal method used to load data for one pair from disk.
@@ -134,7 +133,7 @@ class HDF5DataHandler(IDataHandler):
raise NotImplementedError()
def _trades_load(
- self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
+ self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> pd.DataFrame:
"""
Load a pair from h5 file.
diff --git a/freqtrade/data/history/datahandlers/idatahandler.py b/freqtrade/data/history/datahandlers/idatahandler.py
index 940c4d71e..330620134 100644
--- a/freqtrade/data/history/datahandlers/idatahandler.py
+++ b/freqtrade/data/history/datahandlers/idatahandler.py
@@ -10,7 +10,6 @@ from abc import ABC, abstractmethod
from copy import deepcopy
from datetime import datetime, timezone
from pathlib import Path
-from typing import Optional
from pandas import DataFrame, to_datetime
@@ -126,7 +125,7 @@ class IDataHandler(ABC):
@abstractmethod
def _ohlcv_load(
- self, pair: str, timeframe: str, timerange: Optional[TimeRange], candle_type: CandleType
+ self, pair: str, timeframe: str, timerange: TimeRange | None, candle_type: CandleType
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
@@ -247,7 +246,7 @@ class IDataHandler(ABC):
@abstractmethod
def _trades_load(
- self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
+ self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> DataFrame:
"""
Load a pair from file, either .json.gz or .json
@@ -282,7 +281,7 @@ class IDataHandler(ABC):
return False
def trades_load(
- self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
+ self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> DataFrame:
"""
Load a pair from file, either .json.gz or .json
@@ -370,7 +369,7 @@ class IDataHandler(ABC):
timeframe: str,
candle_type: CandleType,
*,
- timerange: Optional[TimeRange] = None,
+ timerange: TimeRange | None = None,
fill_missing: bool = True,
drop_incomplete: bool = False,
startup_candles: int = 0,
@@ -566,7 +565,7 @@ def get_datahandlerclass(datatype: str) -> type[IDataHandler]:
def get_datahandler(
- datadir: Path, data_format: Optional[str] = None, data_handler: Optional[IDataHandler] = None
+ datadir: Path, data_format: str | None = None, data_handler: IDataHandler | None = None
) -> IDataHandler:
"""
:param datadir: Folder to save data
diff --git a/freqtrade/data/history/datahandlers/jsondatahandler.py b/freqtrade/data/history/datahandlers/jsondatahandler.py
index b97b4b867..1a33b3e2f 100644
--- a/freqtrade/data/history/datahandlers/jsondatahandler.py
+++ b/freqtrade/data/history/datahandlers/jsondatahandler.py
@@ -1,5 +1,4 @@
import logging
-from typing import Optional
import numpy as np
from pandas import DataFrame, read_json, to_datetime
@@ -45,7 +44,7 @@ class JsonDataHandler(IDataHandler):
)
def _ohlcv_load(
- self, pair: str, timeframe: str, timerange: Optional[TimeRange], candle_type: CandleType
+ self, pair: str, timeframe: str, timerange: TimeRange | None, candle_type: CandleType
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
@@ -119,7 +118,7 @@ class JsonDataHandler(IDataHandler):
raise NotImplementedError()
def _trades_load(
- self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
+ self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> DataFrame:
"""
Load a pair from file, either .json.gz or .json
diff --git a/freqtrade/data/history/datahandlers/parquetdatahandler.py b/freqtrade/data/history/datahandlers/parquetdatahandler.py
index e226d4749..82d3babfa 100644
--- a/freqtrade/data/history/datahandlers/parquetdatahandler.py
+++ b/freqtrade/data/history/datahandlers/parquetdatahandler.py
@@ -1,5 +1,4 @@
import logging
-from typing import Optional
from pandas import DataFrame, read_parquet, to_datetime
@@ -35,7 +34,7 @@ class ParquetDataHandler(IDataHandler):
data.reset_index(drop=True).loc[:, self._columns].to_parquet(filename)
def _ohlcv_load(
- self, pair: str, timeframe: str, timerange: Optional[TimeRange], candle_type: CandleType
+ self, pair: str, timeframe: str, timerange: TimeRange | None, candle_type: CandleType
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
@@ -106,7 +105,7 @@ class ParquetDataHandler(IDataHandler):
raise NotImplementedError()
def _trades_load(
- self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
+ self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> DataFrame:
"""
Load a pair from file, either .json.gz or .json
diff --git a/freqtrade/data/history/history_utils.py b/freqtrade/data/history/history_utils.py
index 092faa19a..338b5aeca 100644
--- a/freqtrade/data/history/history_utils.py
+++ b/freqtrade/data/history/history_utils.py
@@ -2,7 +2,6 @@ import logging
import operator
from datetime import datetime, timedelta
from pathlib import Path
-from typing import Optional
from pandas import DataFrame, concat
@@ -37,12 +36,12 @@ def load_pair_history(
timeframe: str,
datadir: Path,
*,
- timerange: Optional[TimeRange] = None,
+ timerange: TimeRange | None = None,
fill_up_missing: bool = True,
drop_incomplete: bool = False,
startup_candles: int = 0,
- data_format: Optional[str] = None,
- data_handler: Optional[IDataHandler] = None,
+ data_format: str | None = None,
+ data_handler: IDataHandler | None = None,
candle_type: CandleType = CandleType.SPOT,
) -> DataFrame:
"""
@@ -79,13 +78,13 @@ def load_data(
timeframe: str,
pairs: list[str],
*,
- timerange: Optional[TimeRange] = None,
+ timerange: TimeRange | None = None,
fill_up_missing: bool = True,
startup_candles: int = 0,
fail_without_data: bool = False,
data_format: str = "feather",
candle_type: CandleType = CandleType.SPOT,
- user_futures_funding_rate: Optional[int] = None,
+ user_futures_funding_rate: int | None = None,
) -> dict[str, DataFrame]:
"""
Load ohlcv history data for a list of pairs.
@@ -137,8 +136,8 @@ def refresh_data(
timeframe: str,
pairs: list[str],
exchange: Exchange,
- data_format: Optional[str] = None,
- timerange: Optional[TimeRange] = None,
+ data_format: str | None = None,
+ timerange: TimeRange | None = None,
candle_type: CandleType,
) -> None:
"""
@@ -168,11 +167,11 @@ def refresh_data(
def _load_cached_data_for_updating(
pair: str,
timeframe: str,
- timerange: Optional[TimeRange],
+ timerange: TimeRange | None,
data_handler: IDataHandler,
candle_type: CandleType,
prepend: bool = False,
-) -> tuple[DataFrame, Optional[int], Optional[int]]:
+) -> tuple[DataFrame, int | None, int | None]:
"""
Load cached data to download more data.
If timerange is passed in, checks whether data from an before the stored data will be
@@ -220,8 +219,8 @@ def _download_pair_history(
exchange: Exchange,
timeframe: str = "5m",
new_pairs_days: int = 30,
- data_handler: Optional[IDataHandler] = None,
- timerange: Optional[TimeRange] = None,
+ data_handler: IDataHandler | None = None,
+ timerange: TimeRange | None = None,
candle_type: CandleType,
erase: bool = False,
prepend: bool = False,
@@ -322,10 +321,10 @@ def refresh_backtest_ohlcv_data(
timeframes: list[str],
datadir: Path,
trading_mode: str,
- timerange: Optional[TimeRange] = None,
+ timerange: TimeRange | None = None,
new_pairs_days: int = 30,
erase: bool = False,
- data_format: Optional[str] = None,
+ data_format: str | None = None,
prepend: bool = False,
) -> list[str]:
"""
@@ -404,7 +403,7 @@ def _download_trades_history(
pair: str,
*,
new_pairs_days: int = 30,
- timerange: Optional[TimeRange] = None,
+ timerange: TimeRange | None = None,
data_handler: IDataHandler,
trading_mode: TradingMode,
) -> bool:
diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py
index c0e46c32a..c17050ec7 100644
--- a/freqtrade/exchange/binance.py
+++ b/freqtrade/exchange/binance.py
@@ -3,7 +3,6 @@
import logging
from datetime import datetime, timezone
from pathlib import Path
-from typing import Optional
import ccxt
@@ -53,7 +52,7 @@ class Binance(Exchange):
(TradingMode.FUTURES, MarginMode.ISOLATED)
]
- def get_tickers(self, symbols: Optional[list[str]] = None, cached: bool = False) -> Tickers:
+ def get_tickers(self, symbols: list[str] | None = None, cached: bool = False) -> Tickers:
tickers = super().get_tickers(symbols=symbols, cached=cached)
if self.trading_mode == TradingMode.FUTURES:
# Binance's future result has no bid/ask values.
@@ -106,7 +105,7 @@ class Binance(Exchange):
candle_type: CandleType,
is_new_pair: bool = False,
raise_: bool = False,
- until_ms: Optional[int] = None,
+ until_ms: int | None = None,
) -> OHLCVResponse:
"""
Overwrite to introduce "fast new pair" functionality by detecting the pair's listing date
@@ -144,9 +143,7 @@ class Binance(Exchange):
"""
return open_date.minute == 0 and open_date.second < 15
- def fetch_funding_rates(
- self, symbols: Optional[list[str]] = None
- ) -> dict[str, dict[str, float]]:
+ def fetch_funding_rates(self, symbols: list[str] | None = None) -> dict[str, dict[str, float]]:
"""
Fetch funding rates for the given symbols.
:param symbols: List of symbols to fetch funding rates for
@@ -177,7 +174,7 @@ class Binance(Exchange):
leverage: float,
wallet_balance: float, # Or margin balance
open_trades: list,
- ) -> Optional[float]:
+ ) -> float | None:
"""
Important: Must be fetching data from cached values as this is used by backtesting!
MARGIN: https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed
diff --git a/freqtrade/exchange/binance_leverage_tiers.json b/freqtrade/exchange/binance_leverage_tiers.json
index e8ad69cc6..71f738c80 100644
--- a/freqtrade/exchange/binance_leverage_tiers.json
+++ b/freqtrade/exchange/binance_leverage_tiers.json
@@ -280,13 +280,13 @@
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
- "maxNotional": 5000.0,
+ "maxNotional": 10000.0,
"maintenanceMarginRate": 0.01,
"maxLeverage": 75.0,
"info": {
"bracket": "1",
"initialLeverage": "75",
- "notionalCap": "5000",
+ "notionalCap": "10000",
"notionalFloor": "0",
"maintMarginRatio": "0.01",
"cum": "0.0"
@@ -295,129 +295,129 @@
{
"tier": 2.0,
"currency": "USDT",
- "minNotional": 5000.0,
- "maxNotional": 10000.0,
+ "minNotional": 10000.0,
+ "maxNotional": 20000.0,
"maintenanceMarginRate": 0.015,
"maxLeverage": 50.0,
"info": {
"bracket": "2",
"initialLeverage": "50",
- "notionalCap": "10000",
- "notionalFloor": "5000",
+ "notionalCap": "20000",
+ "notionalFloor": "10000",
"maintMarginRatio": "0.015",
- "cum": "25.0"
+ "cum": "50.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 10000.0,
- "maxNotional": 30000.0,
+ "minNotional": 20000.0,
+ "maxNotional": 100000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 25.0,
"info": {
"bracket": "3",
"initialLeverage": "25",
- "notionalCap": "30000",
- "notionalFloor": "10000",
+ "notionalCap": "100000",
+ "notionalFloor": "20000",
"maintMarginRatio": "0.02",
- "cum": "75.0"
+ "cum": "150.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 30000.0,
- "maxNotional": 60000.0,
+ "minNotional": 100000.0,
+ "maxNotional": 200000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "4",
"initialLeverage": "20",
- "notionalCap": "60000",
- "notionalFloor": "30000",
+ "notionalCap": "200000",
+ "notionalFloor": "100000",
"maintMarginRatio": "0.025",
- "cum": "225.0"
+ "cum": "650.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 60000.0,
- "maxNotional": 300000.0,
+ "minNotional": 200000.0,
+ "maxNotional": 1000000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "5",
"initialLeverage": "10",
- "notionalCap": "300000",
- "notionalFloor": "60000",
+ "notionalCap": "1000000",
+ "notionalFloor": "200000",
"maintMarginRatio": "0.05",
- "cum": "1725.0"
+ "cum": "5650.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 300000.0,
- "maxNotional": 600000.0,
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "6",
"initialLeverage": "5",
- "notionalCap": "600000",
- "notionalFloor": "300000",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
"maintMarginRatio": "0.1",
- "cum": "16725.0"
+ "cum": "55650.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 600000.0,
- "maxNotional": 750000.0,
+ "minNotional": 2000000.0,
+ "maxNotional": 2500000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "7",
"initialLeverage": "4",
- "notionalCap": "750000",
- "notionalFloor": "600000",
+ "notionalCap": "2500000",
+ "notionalFloor": "2000000",
"maintMarginRatio": "0.125",
- "cum": "31725.0"
+ "cum": "105650.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 750000.0,
- "maxNotional": 1500000.0,
+ "minNotional": 2500000.0,
+ "maxNotional": 5000000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "8",
"initialLeverage": "2",
- "notionalCap": "1500000",
- "notionalFloor": "750000",
+ "notionalCap": "5000000",
+ "notionalFloor": "2500000",
"maintMarginRatio": "0.25",
- "cum": "125475.0"
+ "cum": "418150.0"
}
},
{
"tier": 9.0,
"currency": "USDT",
- "minNotional": 1500000.0,
- "maxNotional": 3000000.0,
+ "minNotional": 5000000.0,
+ "maxNotional": 10000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "9",
"initialLeverage": "1",
- "notionalCap": "3000000",
- "notionalFloor": "1500000",
+ "notionalCap": "10000000",
+ "notionalFloor": "5000000",
"maintMarginRatio": "0.5",
- "cum": "500475.0"
+ "cum": "1668150.0"
}
}
],
@@ -1676,96 +1676,144 @@
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
- "maxNotional": 5000.0,
- "maintenanceMarginRate": 0.012,
- "maxLeverage": 25.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "25",
- "notionalCap": "5000",
+ "initialLeverage": "75",
+ "notionalCap": "10000",
"notionalFloor": "0",
- "maintMarginRatio": "0.012",
+ "maintMarginRatio": "0.01",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
- "minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "minNotional": 10000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "20",
- "notionalCap": "25000",
- "notionalFloor": "5000",
- "maintMarginRatio": "0.025",
- "cum": "65.0"
+ "initialLeverage": "50",
+ "notionalCap": "20000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.015",
+ "cum": "50.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
+ "minNotional": 20000.0,
"maxNotional": 100000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
+ "initialLeverage": "25",
"notionalCap": "100000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "690.0"
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.02",
+ "cum": "150.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
"minNotional": 100000.0,
- "maxNotional": 250000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "250000",
+ "initialLeverage": "20",
+ "notionalCap": "200000",
"notionalFloor": "100000",
- "maintMarginRatio": "0.1",
- "cum": "5690.0"
+ "maintMarginRatio": "0.025",
+ "cum": "650.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 250000.0,
+ "minNotional": 200000.0,
"maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 2.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "2",
+ "initialLeverage": "10",
"notionalCap": "1000000",
- "notionalFloor": "250000",
- "maintMarginRatio": "0.125",
- "cum": "11940.0"
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.05",
+ "cum": "5650.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
"minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.1",
+ "cum": "55650.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 2000000.0,
+ "maxNotional": 2500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "2500000",
+ "notionalFloor": "2000000",
+ "maintMarginRatio": "0.125",
+ "cum": "105650.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 2500000.0,
+ "maxNotional": 5000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "5000000",
+ "notionalFloor": "2500000",
+ "maintMarginRatio": "0.25",
+ "cum": "418150.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 5000000.0,
"maxNotional": 10000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "6",
+ "bracket": "9",
"initialLeverage": "1",
"notionalCap": "10000000",
- "notionalFloor": "1000000",
+ "notionalFloor": "5000000",
"maintMarginRatio": "0.5",
- "cum": "386940.0"
+ "cum": "1668150.0"
}
}
],
@@ -4048,13 +4096,13 @@
"tier": 3.0,
"currency": "USDT",
"minNotional": 50000.0,
- "maxNotional": 80000.0,
+ "maxNotional": 100000.0,
"maintenanceMarginRate": 0.015,
"maxLeverage": 40.0,
"info": {
"bracket": "3",
"initialLeverage": "40",
- "notionalCap": "80000",
+ "notionalCap": "100000",
"notionalFloor": "50000",
"maintMarginRatio": "0.015",
"cum": "337.5"
@@ -4063,113 +4111,113 @@
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 80000.0,
- "maxNotional": 150000.0,
+ "minNotional": 100000.0,
+ "maxNotional": 500000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 25.0,
"info": {
"bracket": "4",
"initialLeverage": "25",
- "notionalCap": "150000",
- "notionalFloor": "80000",
+ "notionalCap": "500000",
+ "notionalFloor": "100000",
"maintMarginRatio": "0.02",
- "cum": "737.5"
+ "cum": "837.5"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 150000.0,
- "maxNotional": 300000.0,
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "5",
"initialLeverage": "20",
- "notionalCap": "300000",
- "notionalFloor": "150000",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
"maintMarginRatio": "0.025",
- "cum": "1487.5"
+ "cum": "3337.5"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 300000.0,
- "maxNotional": 1500000.0,
+ "minNotional": 1000000.0,
+ "maxNotional": 5000000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "6",
"initialLeverage": "10",
- "notionalCap": "1500000",
- "notionalFloor": "300000",
+ "notionalCap": "5000000",
+ "notionalFloor": "1000000",
"maintMarginRatio": "0.05",
- "cum": "8987.5"
+ "cum": "28337.5"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 1500000.0,
- "maxNotional": 3000000.0,
+ "minNotional": 5000000.0,
+ "maxNotional": 10000000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "7",
"initialLeverage": "5",
- "notionalCap": "3000000",
- "notionalFloor": "1500000",
+ "notionalCap": "10000000",
+ "notionalFloor": "5000000",
"maintMarginRatio": "0.1",
- "cum": "83987.5"
+ "cum": "278337.5"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 3000000.0,
- "maxNotional": 3750000.0,
+ "minNotional": 10000000.0,
+ "maxNotional": 12500000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "8",
"initialLeverage": "4",
- "notionalCap": "3750000",
- "notionalFloor": "3000000",
+ "notionalCap": "12500000",
+ "notionalFloor": "10000000",
"maintMarginRatio": "0.125",
- "cum": "158987.5"
+ "cum": "528337.5"
}
},
{
"tier": 9.0,
"currency": "USDT",
- "minNotional": 3750000.0,
- "maxNotional": 7500000.0,
+ "minNotional": 12500000.0,
+ "maxNotional": 25000000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "9",
"initialLeverage": "2",
- "notionalCap": "7500000",
- "notionalFloor": "3750000",
+ "notionalCap": "25000000",
+ "notionalFloor": "12500000",
"maintMarginRatio": "0.25",
- "cum": "627737.5"
+ "cum": "2090837.5"
}
},
{
"tier": 10.0,
"currency": "USDT",
- "minNotional": 7500000.0,
- "maxNotional": 15000000.0,
+ "minNotional": 25000000.0,
+ "maxNotional": 50000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "10",
"initialLeverage": "1",
- "notionalCap": "15000000",
- "notionalFloor": "7500000",
+ "notionalCap": "50000000",
+ "notionalFloor": "25000000",
"maintMarginRatio": "0.5",
- "cum": "2502737.5"
+ "cum": "8340837.5"
}
}
],
@@ -4778,13 +4826,13 @@
"tier": 4.0,
"currency": "USDT",
"minNotional": 80000.0,
- "maxNotional": 200000.0,
+ "maxNotional": 300000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 25.0,
"info": {
"bracket": "4",
"initialLeverage": "25",
- "notionalCap": "200000",
+ "notionalCap": "300000",
"notionalFloor": "80000",
"maintMarginRatio": "0.02",
"cum": "670.0"
@@ -4793,7 +4841,7 @@
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 200000.0,
+ "minNotional": 300000.0,
"maxNotional": 600000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
@@ -4801,89 +4849,89 @@
"bracket": "5",
"initialLeverage": "20",
"notionalCap": "600000",
- "notionalFloor": "200000",
+ "notionalFloor": "300000",
"maintMarginRatio": "0.025",
- "cum": "1670.0"
+ "cum": "2170.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
"minNotional": 600000.0,
- "maxNotional": 2000000.0,
+ "maxNotional": 3000000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "6",
"initialLeverage": "10",
- "notionalCap": "2000000",
+ "notionalCap": "3000000",
"notionalFloor": "600000",
"maintMarginRatio": "0.05",
- "cum": "16670.0"
+ "cum": "17170.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 2000000.0,
- "maxNotional": 4000000.0,
+ "minNotional": 3000000.0,
+ "maxNotional": 6000000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "7",
"initialLeverage": "5",
- "notionalCap": "4000000",
- "notionalFloor": "2000000",
+ "notionalCap": "6000000",
+ "notionalFloor": "3000000",
"maintMarginRatio": "0.1",
- "cum": "116670.0"
+ "cum": "167170.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 4000000.0,
- "maxNotional": 5000000.0,
+ "minNotional": 6000000.0,
+ "maxNotional": 7500000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "8",
"initialLeverage": "4",
- "notionalCap": "5000000",
- "notionalFloor": "4000000",
+ "notionalCap": "7500000",
+ "notionalFloor": "6000000",
"maintMarginRatio": "0.125",
- "cum": "216670.0"
+ "cum": "317170.0"
}
},
{
"tier": 9.0,
"currency": "USDT",
- "minNotional": 5000000.0,
- "maxNotional": 12000000.0,
+ "minNotional": 7500000.0,
+ "maxNotional": 15000000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "9",
"initialLeverage": "2",
- "notionalCap": "12000000",
- "notionalFloor": "5000000",
+ "notionalCap": "15000000",
+ "notionalFloor": "7500000",
"maintMarginRatio": "0.25",
- "cum": "841670.0"
+ "cum": "1254670.0"
}
},
{
"tier": 10.0,
"currency": "USDT",
- "minNotional": 12000000.0,
- "maxNotional": 20000000.0,
+ "minNotional": 15000000.0,
+ "maxNotional": 30000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "10",
"initialLeverage": "1",
- "notionalCap": "20000000",
- "notionalFloor": "12000000",
+ "notionalCap": "30000000",
+ "notionalFloor": "15000000",
"maintMarginRatio": "0.5",
- "cum": "3841670.0"
+ "cum": "5004670.0"
}
}
],
@@ -8010,13 +8058,13 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 10000.0,
- "maxNotional": 20000.0,
+ "maxNotional": 30000.0,
"maintenanceMarginRate": 0.015,
"maxLeverage": 50.0,
"info": {
"bracket": "2",
"initialLeverage": "50",
- "notionalCap": "20000",
+ "notionalCap": "30000",
"notionalFloor": "10000",
"maintMarginRatio": "0.015",
"cum": "50.0"
@@ -8025,113 +8073,113 @@
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 20000.0,
- "maxNotional": 100000.0,
+ "minNotional": 30000.0,
+ "maxNotional": 150000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 25.0,
"info": {
"bracket": "3",
"initialLeverage": "25",
- "notionalCap": "100000",
- "notionalFloor": "20000",
+ "notionalCap": "150000",
+ "notionalFloor": "30000",
"maintMarginRatio": "0.02",
- "cum": "150.0"
+ "cum": "200.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 100000.0,
- "maxNotional": 200000.0,
+ "minNotional": 150000.0,
+ "maxNotional": 300000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "4",
"initialLeverage": "20",
- "notionalCap": "200000",
- "notionalFloor": "100000",
+ "notionalCap": "300000",
+ "notionalFloor": "150000",
"maintMarginRatio": "0.025",
- "cum": "650.0"
+ "cum": "950.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 200000.0,
- "maxNotional": 1000000.0,
+ "minNotional": 300000.0,
+ "maxNotional": 1500000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "5",
"initialLeverage": "10",
- "notionalCap": "1000000",
- "notionalFloor": "200000",
+ "notionalCap": "1500000",
+ "notionalFloor": "300000",
"maintMarginRatio": "0.05",
- "cum": "5650.0"
+ "cum": "8450.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 2000000.0,
+ "minNotional": 1500000.0,
+ "maxNotional": 3000000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "6",
"initialLeverage": "5",
- "notionalCap": "2000000",
- "notionalFloor": "1000000",
+ "notionalCap": "3000000",
+ "notionalFloor": "1500000",
"maintMarginRatio": "0.1",
- "cum": "55650.0"
+ "cum": "83450.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 2000000.0,
- "maxNotional": 2500000.0,
+ "minNotional": 3000000.0,
+ "maxNotional": 3750000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "7",
"initialLeverage": "4",
- "notionalCap": "2500000",
- "notionalFloor": "2000000",
+ "notionalCap": "3750000",
+ "notionalFloor": "3000000",
"maintMarginRatio": "0.125",
- "cum": "105650.0"
+ "cum": "158450.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 2500000.0,
- "maxNotional": 5000000.0,
+ "minNotional": 3750000.0,
+ "maxNotional": 7500000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "8",
"initialLeverage": "2",
- "notionalCap": "5000000",
- "notionalFloor": "2500000",
+ "notionalCap": "7500000",
+ "notionalFloor": "3750000",
"maintMarginRatio": "0.25",
- "cum": "418150.0"
+ "cum": "627200.0"
}
},
{
"tier": 9.0,
"currency": "USDT",
- "minNotional": 5000000.0,
- "maxNotional": 10000000.0,
+ "minNotional": 7500000.0,
+ "maxNotional": 15000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "9",
"initialLeverage": "1",
- "notionalCap": "10000000",
- "notionalFloor": "5000000",
+ "notionalCap": "15000000",
+ "notionalFloor": "7500000",
"maintMarginRatio": "0.5",
- "cum": "1668150.0"
+ "cum": "2502200.0"
}
}
],
@@ -11221,6 +11269,152 @@
}
}
],
+ "CETUS/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "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,
+ "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,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 30000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "30000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 30000.0,
+ "maxNotional": 60000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "60000",
+ "notionalFloor": "30000",
+ "maintMarginRatio": "0.025",
+ "cum": "225.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 60000.0,
+ "maxNotional": 300000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "300000",
+ "notionalFloor": "60000",
+ "maintMarginRatio": "0.05",
+ "cum": "1725.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 300000.0,
+ "maxNotional": 600000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "600000",
+ "notionalFloor": "300000",
+ "maintMarginRatio": "0.1",
+ "cum": "16725.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 600000.0,
+ "maxNotional": 750000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "750000",
+ "notionalFloor": "600000",
+ "maintMarginRatio": "0.125",
+ "cum": "31725.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 750000.0,
+ "maxNotional": 1500000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "1500000",
+ "notionalFloor": "750000",
+ "maintMarginRatio": "0.25",
+ "cum": "125475.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 1500000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "3000000",
+ "notionalFloor": "1500000",
+ "maintMarginRatio": "0.5",
+ "cum": "500475.0"
+ }
+ }
+ ],
"CFX/USDT:USDT": [
{
"tier": 1.0,
@@ -12407,6 +12601,152 @@
}
}
],
+ "COW/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "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,
+ "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,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 30000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "30000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 30000.0,
+ "maxNotional": 60000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "60000",
+ "notionalFloor": "30000",
+ "maintMarginRatio": "0.025",
+ "cum": "225.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 60000.0,
+ "maxNotional": 300000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "300000",
+ "notionalFloor": "60000",
+ "maintMarginRatio": "0.05",
+ "cum": "1725.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 300000.0,
+ "maxNotional": 600000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "600000",
+ "notionalFloor": "300000",
+ "maintMarginRatio": "0.1",
+ "cum": "16725.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 600000.0,
+ "maxNotional": 750000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "750000",
+ "notionalFloor": "600000",
+ "maintMarginRatio": "0.125",
+ "cum": "31725.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 750000.0,
+ "maxNotional": 1500000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "1500000",
+ "notionalFloor": "750000",
+ "maintMarginRatio": "0.25",
+ "cum": "125475.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 1500000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "3000000",
+ "notionalFloor": "1500000",
+ "maintMarginRatio": "0.5",
+ "cum": "500475.0"
+ }
+ }
+ ],
"CRV/USDC:USDC": [
{
"tier": 1.0,
@@ -13162,112 +13502,144 @@
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
- "maxNotional": 5000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 25.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "25",
- "notionalCap": "5000",
+ "initialLeverage": "75",
+ "notionalCap": "10000",
"notionalFloor": "0",
- "maintMarginRatio": "0.02",
+ "maintMarginRatio": "0.01",
"cum": "0.0"
}
},
{
"tier": 2.0,
"currency": "USDT",
- "minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "minNotional": 10000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "20",
- "notionalCap": "25000",
- "notionalFloor": "5000",
- "maintMarginRatio": "0.025",
- "cum": "25.0"
+ "initialLeverage": "50",
+ "notionalCap": "20000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.015",
+ "cum": "50.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 600000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 20000.0,
+ "maxNotional": 100000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "600000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "650.0"
+ "initialLeverage": "25",
+ "notionalCap": "100000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.02",
+ "cum": "150.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 600000.0,
- "maxNotional": 1600000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 100000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "1600000",
- "notionalFloor": "600000",
- "maintMarginRatio": "0.1",
- "cum": "30650.0"
+ "initialLeverage": "20",
+ "notionalCap": "200000",
+ "notionalFloor": "100000",
+ "maintMarginRatio": "0.025",
+ "cum": "650.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 1600000.0,
- "maxNotional": 2000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 200000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "4",
- "notionalCap": "2000000",
- "notionalFloor": "1600000",
- "maintMarginRatio": "0.125",
- "cum": "70650.0"
+ "initialLeverage": "10",
+ "notionalCap": "1000000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.05",
+ "cum": "5650.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 2000000.0,
- "maxNotional": 6000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "2",
- "notionalCap": "6000000",
- "notionalFloor": "2000000",
- "maintMarginRatio": "0.25",
- "cum": "320650.0"
+ "initialLeverage": "5",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.1",
+ "cum": "55650.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
+ "minNotional": 2000000.0,
+ "maxNotional": 2500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "2500000",
+ "notionalFloor": "2000000",
+ "maintMarginRatio": "0.125",
+ "cum": "105650.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 2500000.0,
+ "maxNotional": 6000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "6000000",
+ "notionalFloor": "2500000",
+ "maintMarginRatio": "0.25",
+ "cum": "418150.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
"minNotional": 6000000.0,
"maxNotional": 10000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "7",
+ "bracket": "9",
"initialLeverage": "1",
"notionalCap": "10000000",
"notionalFloor": "6000000",
"maintMarginRatio": "0.5",
- "cum": "1820650.0"
+ "cum": "1918150.0"
}
}
],
@@ -14186,13 +14558,13 @@
"tier": 4.0,
"currency": "USDT",
"minNotional": 750000.0,
- "maxNotional": 800000.0,
+ "maxNotional": 1500000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 25.0,
"info": {
"bracket": "4",
"initialLeverage": "25",
- "notionalCap": "800000",
+ "notionalCap": "1500000",
"notionalFloor": "750000",
"maintMarginRatio": "0.02",
"cum": "7670.0"
@@ -14201,97 +14573,97 @@
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 800000.0,
- "maxNotional": 1600000.0,
+ "minNotional": 1500000.0,
+ "maxNotional": 3000000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "5",
"initialLeverage": "20",
- "notionalCap": "1600000",
- "notionalFloor": "800000",
+ "notionalCap": "3000000",
+ "notionalFloor": "1500000",
"maintMarginRatio": "0.025",
- "cum": "11670.0"
+ "cum": "15170.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1600000.0,
- "maxNotional": 8000000.0,
+ "minNotional": 3000000.0,
+ "maxNotional": 15000000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "6",
"initialLeverage": "10",
- "notionalCap": "8000000",
- "notionalFloor": "1600000",
+ "notionalCap": "15000000",
+ "notionalFloor": "3000000",
"maintMarginRatio": "0.05",
- "cum": "51670.0"
+ "cum": "90170.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 8000000.0,
- "maxNotional": 16000000.0,
+ "minNotional": 15000000.0,
+ "maxNotional": 30000000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "7",
"initialLeverage": "5",
- "notionalCap": "16000000",
- "notionalFloor": "8000000",
+ "notionalCap": "30000000",
+ "notionalFloor": "15000000",
"maintMarginRatio": "0.1",
- "cum": "451670.0"
+ "cum": "840170.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 16000000.0,
- "maxNotional": 20000000.0,
+ "minNotional": 30000000.0,
+ "maxNotional": 37500000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "8",
"initialLeverage": "4",
- "notionalCap": "20000000",
- "notionalFloor": "16000000",
+ "notionalCap": "37500000",
+ "notionalFloor": "30000000",
"maintMarginRatio": "0.125",
- "cum": "851670.0"
+ "cum": "1590170.0"
}
},
{
"tier": 9.0,
"currency": "USDT",
- "minNotional": 20000000.0,
- "maxNotional": 40000000.0,
+ "minNotional": 37500000.0,
+ "maxNotional": 75000000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "9",
"initialLeverage": "2",
- "notionalCap": "40000000",
- "notionalFloor": "20000000",
+ "notionalCap": "75000000",
+ "notionalFloor": "37500000",
"maintMarginRatio": "0.25",
- "cum": "3351670.0"
+ "cum": "6277670.0"
}
},
{
"tier": 10.0,
"currency": "USDT",
- "minNotional": 40000000.0,
- "maxNotional": 80000000.0,
+ "minNotional": 75000000.0,
+ "maxNotional": 150000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "10",
"initialLeverage": "1",
- "notionalCap": "80000000",
- "notionalFloor": "40000000",
+ "notionalCap": "150000000",
+ "notionalFloor": "75000000",
"maintMarginRatio": "0.5",
- "cum": "13351670.0"
+ "cum": "25027670.0"
}
}
],
@@ -15112,13 +15484,13 @@
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
- "maxNotional": 5000.0,
+ "maxNotional": 10000.0,
"maintenanceMarginRate": 0.01,
- "maxLeverage": 25.0,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "25",
- "notionalCap": "5000",
+ "initialLeverage": "75",
+ "notionalCap": "10000",
"notionalFloor": "0",
"maintMarginRatio": "0.01",
"cum": "0.0"
@@ -15127,97 +15499,129 @@
{
"tier": 2.0,
"currency": "USDT",
- "minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "minNotional": 10000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "20",
- "notionalCap": "25000",
- "notionalFloor": "5000",
- "maintMarginRatio": "0.025",
- "cum": "75.0"
+ "initialLeverage": "50",
+ "notionalCap": "20000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.015",
+ "cum": "50.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 600000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 20000.0,
+ "maxNotional": 100000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "600000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "700.0"
+ "initialLeverage": "25",
+ "notionalCap": "100000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.02",
+ "cum": "150.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 600000.0,
- "maxNotional": 1600000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 100000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "1600000",
- "notionalFloor": "600000",
- "maintMarginRatio": "0.1",
- "cum": "30700.0"
+ "initialLeverage": "20",
+ "notionalCap": "200000",
+ "notionalFloor": "100000",
+ "maintMarginRatio": "0.025",
+ "cum": "650.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 1600000.0,
- "maxNotional": 2000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 200000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "4",
- "notionalCap": "2000000",
- "notionalFloor": "1600000",
- "maintMarginRatio": "0.125",
- "cum": "70700.0"
+ "initialLeverage": "10",
+ "notionalCap": "1000000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.05",
+ "cum": "5650.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 2000000.0,
- "maxNotional": 6000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "2",
- "notionalCap": "6000000",
- "notionalFloor": "2000000",
- "maintMarginRatio": "0.25",
- "cum": "320700.0"
+ "initialLeverage": "5",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.1",
+ "cum": "55650.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
+ "minNotional": 2000000.0,
+ "maxNotional": 2500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "2500000",
+ "notionalFloor": "2000000",
+ "maintMarginRatio": "0.125",
+ "cum": "105650.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 2500000.0,
+ "maxNotional": 6000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "6000000",
+ "notionalFloor": "2500000",
+ "maintMarginRatio": "0.25",
+ "cum": "418150.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
"minNotional": 6000000.0,
"maxNotional": 10000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "7",
+ "bracket": "9",
"initialLeverage": "1",
"notionalCap": "10000000",
"notionalFloor": "6000000",
"maintMarginRatio": "0.5",
- "cum": "1820700.0"
+ "cum": "1918150.0"
}
}
],
@@ -18294,10 +18698,10 @@
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.01,
- "maxLeverage": 20.0,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "20",
+ "initialLeverage": "75",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.01",
@@ -18308,80 +18712,128 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 10.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "10",
- "notionalCap": "25000",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
"notionalFloor": "5000",
- "maintMarginRatio": "0.025",
- "cum": "75.0"
+ "maintMarginRatio": "0.015",
+ "cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 100000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 8.0,
+ "minNotional": 10000.0,
+ "maxNotional": 50000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "8",
- "notionalCap": "100000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "700.0"
+ "initialLeverage": "25",
+ "notionalCap": "50000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 100000.0,
- "maxNotional": 250000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 50000.0,
+ "maxNotional": 100000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "250000",
- "notionalFloor": "100000",
- "maintMarginRatio": "0.1",
- "cum": "5700.0"
+ "initialLeverage": "20",
+ "notionalCap": "100000",
+ "notionalFloor": "50000",
+ "maintMarginRatio": "0.025",
+ "cum": "325.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 250000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 2.0,
+ "minNotional": 100000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "2",
- "notionalCap": "1000000",
- "notionalFloor": "250000",
- "maintMarginRatio": "0.125",
- "cum": "11950.0"
+ "initialLeverage": "10",
+ "notionalCap": "500000",
+ "notionalFloor": "100000",
+ "maintMarginRatio": "0.05",
+ "cum": "2825.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.1",
+ "cum": "27825.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
"minNotional": 1000000.0,
+ "maxNotional": 1250000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "1250000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.125",
+ "cum": "52825.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1250000.0,
+ "maxNotional": 2500000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "2500000",
+ "notionalFloor": "1250000",
+ "maintMarginRatio": "0.25",
+ "cum": "209075.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 2500000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "6",
+ "bracket": "9",
"initialLeverage": "1",
"notionalCap": "5000000",
- "notionalFloor": "1000000",
+ "notionalFloor": "2500000",
"maintMarginRatio": "0.5",
- "cum": "386950.0"
+ "cum": "834075.0"
}
}
],
@@ -19913,6 +20365,152 @@
}
}
],
+ "GOAT/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "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,
+ "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,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 30000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "30000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 30000.0,
+ "maxNotional": 60000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "60000",
+ "notionalFloor": "30000",
+ "maintMarginRatio": "0.025",
+ "cum": "225.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 60000.0,
+ "maxNotional": 300000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "300000",
+ "notionalFloor": "60000",
+ "maintMarginRatio": "0.05",
+ "cum": "1725.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 300000.0,
+ "maxNotional": 600000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "600000",
+ "notionalFloor": "300000",
+ "maintMarginRatio": "0.1",
+ "cum": "16725.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 600000.0,
+ "maxNotional": 750000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "750000",
+ "notionalFloor": "600000",
+ "maintMarginRatio": "0.125",
+ "cum": "31725.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 750000.0,
+ "maxNotional": 1500000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "1500000",
+ "notionalFloor": "750000",
+ "maintMarginRatio": "0.25",
+ "cum": "125475.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 1500000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "3000000",
+ "notionalFloor": "1500000",
+ "maintMarginRatio": "0.5",
+ "cum": "500475.0"
+ }
+ }
+ ],
"GRT/USDT:USDT": [
{
"tier": 1.0,
@@ -20874,114 +21472,16 @@
}
],
"HOT/USDT:USDT": [
- {
- "tier": 1.0,
- "currency": "USDT",
- "minNotional": 0.0,
- "maxNotional": 5000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 25.0,
- "info": {
- "bracket": "1",
- "initialLeverage": "25",
- "notionalCap": "5000",
- "notionalFloor": "0",
- "maintMarginRatio": "0.02",
- "cum": "0.0"
- }
- },
- {
- "tier": 2.0,
- "currency": "USDT",
- "minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
- "info": {
- "bracket": "2",
- "initialLeverage": "20",
- "notionalCap": "25000",
- "notionalFloor": "5000",
- "maintMarginRatio": "0.025",
- "cum": "25.0"
- }
- },
- {
- "tier": 3.0,
- "currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 100000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
- "info": {
- "bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "100000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "650.0"
- }
- },
- {
- "tier": 4.0,
- "currency": "USDT",
- "minNotional": 100000.0,
- "maxNotional": 250000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
- "info": {
- "bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "250000",
- "notionalFloor": "100000",
- "maintMarginRatio": "0.1",
- "cum": "5650.0"
- }
- },
- {
- "tier": 5.0,
- "currency": "USDT",
- "minNotional": 250000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 2.0,
- "info": {
- "bracket": "5",
- "initialLeverage": "2",
- "notionalCap": "1000000",
- "notionalFloor": "250000",
- "maintMarginRatio": "0.125",
- "cum": "11900.0"
- }
- },
- {
- "tier": 6.0,
- "currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 5000000.0,
- "maintenanceMarginRate": 0.5,
- "maxLeverage": 1.0,
- "info": {
- "bracket": "6",
- "initialLeverage": "1",
- "notionalCap": "5000000",
- "notionalFloor": "1000000",
- "maintMarginRatio": "0.5",
- "cum": "386900.0"
- }
- }
- ],
- "ICP/USDT:USDT": [
{
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.01,
- "maxLeverage": 25.0,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "25",
+ "initialLeverage": "75",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.01",
@@ -20992,96 +21492,274 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "20",
- "notionalCap": "25000",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
"notionalFloor": "5000",
- "maintMarginRatio": "0.025",
- "cum": "75.0"
+ "maintMarginRatio": "0.015",
+ "cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 600000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 10000.0,
+ "maxNotional": 50000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "600000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "700.0"
+ "initialLeverage": "25",
+ "notionalCap": "50000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 600000.0,
- "maxNotional": 1600000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 50000.0,
+ "maxNotional": 100000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "1600000",
- "notionalFloor": "600000",
- "maintMarginRatio": "0.1",
- "cum": "30700.0"
+ "initialLeverage": "20",
+ "notionalCap": "100000",
+ "notionalFloor": "50000",
+ "maintMarginRatio": "0.025",
+ "cum": "325.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 1600000.0,
- "maxNotional": 2000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 100000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "4",
- "notionalCap": "2000000",
- "notionalFloor": "1600000",
- "maintMarginRatio": "0.125",
- "cum": "70700.0"
+ "initialLeverage": "10",
+ "notionalCap": "500000",
+ "notionalFloor": "100000",
+ "maintMarginRatio": "0.05",
+ "cum": "2825.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 2000000.0,
- "maxNotional": 6000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "2",
- "notionalCap": "6000000",
- "notionalFloor": "2000000",
- "maintMarginRatio": "0.25",
- "cum": "320700.0"
+ "initialLeverage": "5",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.1",
+ "cum": "27825.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 1250000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "1250000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.125",
+ "cum": "52825.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1250000.0,
+ "maxNotional": 2500000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "2500000",
+ "notionalFloor": "1250000",
+ "maintMarginRatio": "0.25",
+ "cum": "209075.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 2500000.0,
+ "maxNotional": 5000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "5000000",
+ "notionalFloor": "2500000",
+ "maintMarginRatio": "0.5",
+ "cum": "834075.0"
+ }
+ }
+ ],
+ "ICP/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "currency": "USDT",
+ "minNotional": 0.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
+ "info": {
+ "bracket": "1",
+ "initialLeverage": "75",
+ "notionalCap": "10000",
+ "notionalFloor": "0",
+ "maintMarginRatio": "0.01",
+ "cum": "0.0"
+ }
+ },
+ {
+ "tier": 2.0,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 20000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
+ "info": {
+ "bracket": "2",
+ "initialLeverage": "50",
+ "notionalCap": "20000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.015",
+ "cum": "50.0"
+ }
+ },
+ {
+ "tier": 3.0,
+ "currency": "USDT",
+ "minNotional": 20000.0,
+ "maxNotional": 100000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "100000",
+ "notionalFloor": "20000",
+ "maintMarginRatio": "0.02",
+ "cum": "150.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 100000.0,
+ "maxNotional": 200000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "200000",
+ "notionalFloor": "100000",
+ "maintMarginRatio": "0.025",
+ "cum": "650.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 200000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "1000000",
+ "notionalFloor": "200000",
+ "maintMarginRatio": "0.05",
+ "cum": "5650.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 2000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "2000000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.1",
+ "cum": "55650.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 2000000.0,
+ "maxNotional": 2500000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "2500000",
+ "notionalFloor": "2000000",
+ "maintMarginRatio": "0.125",
+ "cum": "105650.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 2500000.0,
+ "maxNotional": 6000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "6000000",
+ "notionalFloor": "2500000",
+ "maintMarginRatio": "0.25",
+ "cum": "418150.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
"minNotional": 6000000.0,
"maxNotional": 10000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "7",
+ "bracket": "9",
"initialLeverage": "1",
"notionalCap": "10000000",
"notionalFloor": "6000000",
"maintMarginRatio": "0.5",
- "cum": "1820700.0"
+ "cum": "1918150.0"
}
}
],
@@ -21221,14 +21899,14 @@
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 20.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "20",
+ "initialLeverage": "75",
"notionalCap": "5000",
"notionalFloor": "0",
- "maintMarginRatio": "0.02",
+ "maintMarginRatio": "0.01",
"cum": "0.0"
}
},
@@ -21236,96 +21914,128 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 15.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "15",
- "notionalCap": "25000",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
"notionalFloor": "5000",
- "maintMarginRatio": "0.025",
+ "maintMarginRatio": "0.015",
"cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 200000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 10000.0,
+ "maxNotional": 50000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "200000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "650.0"
+ "initialLeverage": "25",
+ "notionalCap": "50000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 200000.0,
- "maxNotional": 500000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 50000.0,
+ "maxNotional": 100000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "500000",
- "notionalFloor": "200000",
- "maintMarginRatio": "0.1",
- "cum": "10650.0"
+ "initialLeverage": "20",
+ "notionalCap": "100000",
+ "notionalFloor": "50000",
+ "maintMarginRatio": "0.025",
+ "cum": "325.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 500000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 100000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "4",
- "notionalCap": "1000000",
- "notionalFloor": "500000",
- "maintMarginRatio": "0.125",
- "cum": "23150.0"
+ "initialLeverage": "10",
+ "notionalCap": "500000",
+ "notionalFloor": "100000",
+ "maintMarginRatio": "0.05",
+ "cum": "2825.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 3000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "2",
- "notionalCap": "3000000",
- "notionalFloor": "1000000",
- "maintMarginRatio": "0.25",
- "cum": "148150.0"
+ "initialLeverage": "5",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.1",
+ "cum": "27825.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 1250000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "1250000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.125",
+ "cum": "52825.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1250000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "3000000",
+ "notionalFloor": "1250000",
+ "maintMarginRatio": "0.25",
+ "cum": "209075.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
"minNotional": 3000000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "7",
+ "bracket": "9",
"initialLeverage": "1",
"notionalCap": "5000000",
"notionalFloor": "3000000",
"maintMarginRatio": "0.5",
- "cum": "898150.0"
+ "cum": "959075.0"
}
}
],
@@ -25837,14 +26547,14 @@
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 20.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "20",
+ "initialLeverage": "75",
"notionalCap": "5000",
"notionalFloor": "0",
- "maintMarginRatio": "0.02",
+ "maintMarginRatio": "0.01",
"cum": "0.0"
}
},
@@ -25852,80 +26562,128 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 15.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "15",
- "notionalCap": "25000",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
"notionalFloor": "5000",
- "maintMarginRatio": "0.025",
+ "maintMarginRatio": "0.015",
"cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 100000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 10000.0,
+ "maxNotional": 50000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "100000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "650.0"
+ "initialLeverage": "25",
+ "notionalCap": "50000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 100000.0,
- "maxNotional": 250000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 50000.0,
+ "maxNotional": 100000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "250000",
- "notionalFloor": "100000",
- "maintMarginRatio": "0.1",
- "cum": "5650.0"
+ "initialLeverage": "20",
+ "notionalCap": "100000",
+ "notionalFloor": "50000",
+ "maintMarginRatio": "0.025",
+ "cum": "325.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 250000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 2.0,
+ "minNotional": 100000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "2",
- "notionalCap": "1000000",
- "notionalFloor": "250000",
- "maintMarginRatio": "0.125",
- "cum": "11900.0"
+ "initialLeverage": "10",
+ "notionalCap": "500000",
+ "notionalFloor": "100000",
+ "maintMarginRatio": "0.05",
+ "cum": "2825.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.1",
+ "cum": "27825.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
"minNotional": 1000000.0,
+ "maxNotional": 1250000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "1250000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.125",
+ "cum": "52825.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1250000.0,
+ "maxNotional": 2500000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "2500000",
+ "notionalFloor": "1250000",
+ "maintMarginRatio": "0.25",
+ "cum": "209075.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 2500000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "6",
+ "bracket": "9",
"initialLeverage": "1",
"notionalCap": "5000000",
- "notionalFloor": "1000000",
+ "notionalFloor": "2500000",
"maintMarginRatio": "0.5",
- "cum": "386900.0"
+ "cum": "834075.0"
}
}
],
@@ -26829,14 +27587,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"
}
},
@@ -26844,112 +27602,128 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 25.0,
+ "maxNotional": 16000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "25",
- "notionalCap": "25000",
+ "initialLeverage": "50",
+ "notionalCap": "16000",
"notionalFloor": "5000",
- "maintMarginRatio": "0.02",
+ "maintMarginRatio": "0.015",
"cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
+ "minNotional": 16000.0,
"maxNotional": 80000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "20",
+ "initialLeverage": "25",
"notionalCap": "80000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.025",
- "cum": "150.0"
+ "notionalFloor": "16000",
+ "maintMarginRatio": "0.02",
+ "cum": "105.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
"minNotional": 80000.0,
- "maxNotional": 800000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "maxNotional": 160000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "10",
- "notionalCap": "800000",
+ "initialLeverage": "20",
+ "notionalCap": "160000",
"notionalFloor": "80000",
- "maintMarginRatio": "0.05",
- "cum": "2150.0"
+ "maintMarginRatio": "0.025",
+ "cum": "505.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
+ "minNotional": 160000.0,
+ "maxNotional": 800000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "800000",
+ "notionalFloor": "160000",
+ "maintMarginRatio": "0.05",
+ "cum": "4505.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
"minNotional": 800000.0,
"maxNotional": 1600000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
- "bracket": "5",
+ "bracket": "6",
"initialLeverage": "5",
"notionalCap": "1600000",
"notionalFloor": "800000",
"maintMarginRatio": "0.1",
- "cum": "42150.0"
+ "cum": "44505.0"
}
},
{
- "tier": 6.0,
+ "tier": 7.0,
"currency": "USDT",
"minNotional": 1600000.0,
"maxNotional": 2000000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
- "bracket": "6",
+ "bracket": "7",
"initialLeverage": "4",
"notionalCap": "2000000",
"notionalFloor": "1600000",
"maintMarginRatio": "0.125",
- "cum": "82150.0"
+ "cum": "84505.0"
}
},
{
- "tier": 7.0,
+ "tier": 8.0,
"currency": "USDT",
"minNotional": 2000000.0,
"maxNotional": 4000000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
- "bracket": "7",
+ "bracket": "8",
"initialLeverage": "2",
"notionalCap": "4000000",
"notionalFloor": "2000000",
"maintMarginRatio": "0.25",
- "cum": "332150.0"
+ "cum": "334505.0"
}
},
{
- "tier": 8.0,
+ "tier": 9.0,
"currency": "USDT",
"minNotional": 4000000.0,
"maxNotional": 8000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "8",
+ "bracket": "9",
"initialLeverage": "1",
"notionalCap": "8000000",
"notionalFloor": "4000000",
"maintMarginRatio": "0.5",
- "cum": "1332150.0"
+ "cum": "1334505.0"
}
}
],
@@ -27088,13 +27862,13 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 10000.0,
- "maxNotional": 30000.0,
+ "maxNotional": 60000.0,
"maintenanceMarginRate": 0.015,
"maxLeverage": 50.0,
"info": {
"bracket": "2",
"initialLeverage": "50",
- "notionalCap": "30000",
+ "notionalCap": "60000",
"notionalFloor": "10000",
"maintMarginRatio": "0.015",
"cum": "50.0"
@@ -27103,113 +27877,113 @@
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 30000.0,
- "maxNotional": 150000.0,
+ "minNotional": 60000.0,
+ "maxNotional": 300000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 25.0,
"info": {
"bracket": "3",
"initialLeverage": "25",
- "notionalCap": "150000",
- "notionalFloor": "30000",
+ "notionalCap": "300000",
+ "notionalFloor": "60000",
"maintMarginRatio": "0.02",
- "cum": "200.0"
+ "cum": "350.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 150000.0,
- "maxNotional": 300000.0,
+ "minNotional": 300000.0,
+ "maxNotional": 600000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "4",
"initialLeverage": "20",
- "notionalCap": "300000",
- "notionalFloor": "150000",
+ "notionalCap": "600000",
+ "notionalFloor": "300000",
"maintMarginRatio": "0.025",
- "cum": "950.0"
+ "cum": "1850.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 300000.0,
- "maxNotional": 1500000.0,
+ "minNotional": 600000.0,
+ "maxNotional": 3000000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "5",
"initialLeverage": "10",
- "notionalCap": "1500000",
- "notionalFloor": "300000",
+ "notionalCap": "3000000",
+ "notionalFloor": "600000",
"maintMarginRatio": "0.05",
- "cum": "8450.0"
+ "cum": "16850.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1500000.0,
- "maxNotional": 3000000.0,
+ "minNotional": 3000000.0,
+ "maxNotional": 6000000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "6",
"initialLeverage": "5",
- "notionalCap": "3000000",
- "notionalFloor": "1500000",
+ "notionalCap": "6000000",
+ "notionalFloor": "3000000",
"maintMarginRatio": "0.1",
- "cum": "83450.0"
+ "cum": "166850.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 3000000.0,
- "maxNotional": 3750000.0,
+ "minNotional": 6000000.0,
+ "maxNotional": 7500000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "7",
"initialLeverage": "4",
- "notionalCap": "3750000",
- "notionalFloor": "3000000",
+ "notionalCap": "7500000",
+ "notionalFloor": "6000000",
"maintMarginRatio": "0.125",
- "cum": "158450.0"
+ "cum": "316850.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 3750000.0,
- "maxNotional": 7500000.0,
+ "minNotional": 7500000.0,
+ "maxNotional": 15000000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "8",
"initialLeverage": "2",
- "notionalCap": "7500000",
- "notionalFloor": "3750000",
+ "notionalCap": "15000000",
+ "notionalFloor": "7500000",
"maintMarginRatio": "0.25",
- "cum": "627200.0"
+ "cum": "1254350.0"
}
},
{
"tier": 9.0,
"currency": "USDT",
- "minNotional": 7500000.0,
- "maxNotional": 15000000.0,
+ "minNotional": 15000000.0,
+ "maxNotional": 30000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "9",
"initialLeverage": "1",
- "notionalCap": "15000000",
- "notionalFloor": "7500000",
+ "notionalCap": "30000000",
+ "notionalFloor": "15000000",
"maintMarginRatio": "0.5",
- "cum": "2502200.0"
+ "cum": "5004350.0"
}
}
],
@@ -27489,6 +28263,152 @@
}
}
],
+ "MOODENG/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "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,
+ "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,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 30000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "30000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 30000.0,
+ "maxNotional": 60000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "60000",
+ "notionalFloor": "30000",
+ "maintMarginRatio": "0.025",
+ "cum": "225.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 60000.0,
+ "maxNotional": 300000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "300000",
+ "notionalFloor": "60000",
+ "maintMarginRatio": "0.05",
+ "cum": "1725.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 300000.0,
+ "maxNotional": 600000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "600000",
+ "notionalFloor": "300000",
+ "maintMarginRatio": "0.1",
+ "cum": "16725.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 600000.0,
+ "maxNotional": 750000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "750000",
+ "notionalFloor": "600000",
+ "maintMarginRatio": "0.125",
+ "cum": "31725.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 750000.0,
+ "maxNotional": 1500000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "1500000",
+ "notionalFloor": "750000",
+ "maintMarginRatio": "0.25",
+ "cum": "125475.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 1500000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "3000000",
+ "notionalFloor": "1500000",
+ "maintMarginRatio": "0.5",
+ "cum": "500475.0"
+ }
+ }
+ ],
"MOVR/USDT:USDT": [
{
"tier": 1.0,
@@ -31817,14 +32737,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"
}
},
@@ -31832,112 +32752,128 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 25.0,
+ "maxNotional": 16000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "25",
- "notionalCap": "25000",
+ "initialLeverage": "50",
+ "notionalCap": "16000",
"notionalFloor": "5000",
- "maintMarginRatio": "0.02",
+ "maintMarginRatio": "0.015",
"cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
+ "minNotional": 16000.0,
"maxNotional": 80000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "20",
+ "initialLeverage": "25",
"notionalCap": "80000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.025",
- "cum": "150.0"
+ "notionalFloor": "16000",
+ "maintMarginRatio": "0.02",
+ "cum": "105.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
"minNotional": 80000.0,
- "maxNotional": 800000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "maxNotional": 160000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "10",
- "notionalCap": "800000",
+ "initialLeverage": "20",
+ "notionalCap": "160000",
"notionalFloor": "80000",
- "maintMarginRatio": "0.05",
- "cum": "2150.0"
+ "maintMarginRatio": "0.025",
+ "cum": "505.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
+ "minNotional": 160000.0,
+ "maxNotional": 800000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "800000",
+ "notionalFloor": "160000",
+ "maintMarginRatio": "0.05",
+ "cum": "4505.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
"minNotional": 800000.0,
"maxNotional": 1600000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
- "bracket": "5",
+ "bracket": "6",
"initialLeverage": "5",
"notionalCap": "1600000",
"notionalFloor": "800000",
"maintMarginRatio": "0.1",
- "cum": "42150.0"
+ "cum": "44505.0"
}
},
{
- "tier": 6.0,
+ "tier": 7.0,
"currency": "USDT",
"minNotional": 1600000.0,
"maxNotional": 2000000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
- "bracket": "6",
+ "bracket": "7",
"initialLeverage": "4",
"notionalCap": "2000000",
"notionalFloor": "1600000",
"maintMarginRatio": "0.125",
- "cum": "82150.0"
+ "cum": "84505.0"
}
},
{
- "tier": 7.0,
+ "tier": 8.0,
"currency": "USDT",
"minNotional": 2000000.0,
"maxNotional": 4000000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
- "bracket": "7",
+ "bracket": "8",
"initialLeverage": "2",
"notionalCap": "4000000",
"notionalFloor": "2000000",
"maintMarginRatio": "0.25",
- "cum": "332150.0"
+ "cum": "334505.0"
}
},
{
- "tier": 8.0,
+ "tier": 9.0,
"currency": "USDT",
"minNotional": 4000000.0,
"maxNotional": 8000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "8",
+ "bracket": "9",
"initialLeverage": "1",
"notionalCap": "8000000",
"notionalFloor": "4000000",
"maintMarginRatio": "0.5",
- "cum": "1332150.0"
+ "cum": "1334505.0"
}
}
],
@@ -32217,6 +33153,152 @@
}
}
],
+ "PONKE/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "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,
+ "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,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 30000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "30000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 30000.0,
+ "maxNotional": 60000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "60000",
+ "notionalFloor": "30000",
+ "maintMarginRatio": "0.025",
+ "cum": "225.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 60000.0,
+ "maxNotional": 300000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "300000",
+ "notionalFloor": "60000",
+ "maintMarginRatio": "0.05",
+ "cum": "1725.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 300000.0,
+ "maxNotional": 600000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "600000",
+ "notionalFloor": "300000",
+ "maintMarginRatio": "0.1",
+ "cum": "16725.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 600000.0,
+ "maxNotional": 750000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "750000",
+ "notionalFloor": "600000",
+ "maintMarginRatio": "0.125",
+ "cum": "31725.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 750000.0,
+ "maxNotional": 1500000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "1500000",
+ "notionalFloor": "750000",
+ "maintMarginRatio": "0.25",
+ "cum": "125475.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 1500000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "3000000",
+ "notionalFloor": "1500000",
+ "maintMarginRatio": "0.5",
+ "cum": "500475.0"
+ }
+ }
+ ],
"POPCAT/USDT:USDT": [
{
"tier": 1.0,
@@ -34582,10 +35664,10 @@
"minNotional": 0.0,
"maxNotional": 5000.0,
"maintenanceMarginRate": 0.01,
- "maxLeverage": 25.0,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "25",
+ "initialLeverage": "75",
"notionalCap": "5000",
"notionalFloor": "0",
"maintMarginRatio": "0.01",
@@ -34596,96 +35678,128 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "20",
- "notionalCap": "25000",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
"notionalFloor": "5000",
- "maintMarginRatio": "0.025",
- "cum": "75.0"
+ "maintMarginRatio": "0.015",
+ "cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 200000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 10000.0,
+ "maxNotional": 50000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "200000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "700.0"
+ "initialLeverage": "25",
+ "notionalCap": "50000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 200000.0,
- "maxNotional": 500000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 50000.0,
+ "maxNotional": 100000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "500000",
- "notionalFloor": "200000",
- "maintMarginRatio": "0.1",
- "cum": "10700.0"
+ "initialLeverage": "20",
+ "notionalCap": "100000",
+ "notionalFloor": "50000",
+ "maintMarginRatio": "0.025",
+ "cum": "325.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 500000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 100000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "4",
- "notionalCap": "1000000",
- "notionalFloor": "500000",
- "maintMarginRatio": "0.125",
- "cum": "23200.0"
+ "initialLeverage": "10",
+ "notionalCap": "500000",
+ "notionalFloor": "100000",
+ "maintMarginRatio": "0.05",
+ "cum": "2825.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 3000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "2",
- "notionalCap": "3000000",
- "notionalFloor": "1000000",
- "maintMarginRatio": "0.25",
- "cum": "148200.0"
+ "initialLeverage": "5",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.1",
+ "cum": "27825.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 1250000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "1250000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.125",
+ "cum": "52825.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1250000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "3000000",
+ "notionalFloor": "1250000",
+ "maintMarginRatio": "0.25",
+ "cum": "209075.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
"minNotional": 3000000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "7",
+ "bracket": "9",
"initialLeverage": "1",
"notionalCap": "5000000",
"notionalFloor": "3000000",
"maintMarginRatio": "0.5",
- "cum": "898200.0"
+ "cum": "959075.0"
}
}
],
@@ -34841,14 +35955,14 @@
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 25.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "25",
+ "initialLeverage": "75",
"notionalCap": "5000",
"notionalFloor": "0",
- "maintMarginRatio": "0.02",
+ "maintMarginRatio": "0.01",
"cum": "0.0"
}
},
@@ -34856,96 +35970,128 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "20",
- "notionalCap": "25000",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
"notionalFloor": "5000",
- "maintMarginRatio": "0.025",
+ "maintMarginRatio": "0.015",
"cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 300000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 10000.0,
+ "maxNotional": 50000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "300000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "650.0"
+ "initialLeverage": "25",
+ "notionalCap": "50000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 300000.0,
- "maxNotional": 800000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 50000.0,
+ "maxNotional": 100000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "800000",
- "notionalFloor": "300000",
- "maintMarginRatio": "0.1",
- "cum": "15650.0"
+ "initialLeverage": "20",
+ "notionalCap": "100000",
+ "notionalFloor": "50000",
+ "maintMarginRatio": "0.025",
+ "cum": "325.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 800000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 100000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "4",
- "notionalCap": "1000000",
- "notionalFloor": "800000",
- "maintMarginRatio": "0.125",
- "cum": "35650.0"
+ "initialLeverage": "10",
+ "notionalCap": "500000",
+ "notionalFloor": "100000",
+ "maintMarginRatio": "0.05",
+ "cum": "2825.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 3000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "2",
- "notionalCap": "3000000",
- "notionalFloor": "1000000",
- "maintMarginRatio": "0.25",
- "cum": "160650.0"
+ "initialLeverage": "5",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.1",
+ "cum": "27825.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 1250000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "1250000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.125",
+ "cum": "52825.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1250000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "3000000",
+ "notionalFloor": "1250000",
+ "maintMarginRatio": "0.25",
+ "cum": "209075.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
"minNotional": 3000000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "7",
+ "bracket": "9",
"initialLeverage": "1",
"notionalCap": "5000000",
"notionalFloor": "3000000",
"maintMarginRatio": "0.5",
- "cum": "910650.0"
+ "cum": "959075.0"
}
}
],
@@ -34970,13 +36116,13 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 10000.0,
- "maxNotional": 20000.0,
+ "maxNotional": 30000.0,
"maintenanceMarginRate": 0.015,
"maxLeverage": 50.0,
"info": {
"bracket": "2",
"initialLeverage": "50",
- "notionalCap": "20000",
+ "notionalCap": "30000",
"notionalFloor": "10000",
"maintMarginRatio": "0.015",
"cum": "50.0"
@@ -34985,113 +36131,113 @@
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 20000.0,
- "maxNotional": 100000.0,
+ "minNotional": 30000.0,
+ "maxNotional": 150000.0,
"maintenanceMarginRate": 0.02,
"maxLeverage": 25.0,
"info": {
"bracket": "3",
"initialLeverage": "25",
- "notionalCap": "100000",
- "notionalFloor": "20000",
+ "notionalCap": "150000",
+ "notionalFloor": "30000",
"maintMarginRatio": "0.02",
- "cum": "150.0"
+ "cum": "200.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 100000.0,
- "maxNotional": 200000.0,
+ "minNotional": 150000.0,
+ "maxNotional": 300000.0,
"maintenanceMarginRate": 0.025,
"maxLeverage": 20.0,
"info": {
"bracket": "4",
"initialLeverage": "20",
- "notionalCap": "200000",
- "notionalFloor": "100000",
+ "notionalCap": "300000",
+ "notionalFloor": "150000",
"maintMarginRatio": "0.025",
- "cum": "650.0"
+ "cum": "950.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 200000.0,
- "maxNotional": 1000000.0,
+ "minNotional": 300000.0,
+ "maxNotional": 1500000.0,
"maintenanceMarginRate": 0.05,
"maxLeverage": 10.0,
"info": {
"bracket": "5",
"initialLeverage": "10",
- "notionalCap": "1000000",
- "notionalFloor": "200000",
+ "notionalCap": "1500000",
+ "notionalFloor": "300000",
"maintMarginRatio": "0.05",
- "cum": "5650.0"
+ "cum": "8450.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 2000000.0,
+ "minNotional": 1500000.0,
+ "maxNotional": 3000000.0,
"maintenanceMarginRate": 0.1,
"maxLeverage": 5.0,
"info": {
"bracket": "6",
"initialLeverage": "5",
- "notionalCap": "2000000",
- "notionalFloor": "1000000",
+ "notionalCap": "3000000",
+ "notionalFloor": "1500000",
"maintMarginRatio": "0.1",
- "cum": "55650.0"
+ "cum": "83450.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
- "minNotional": 2000000.0,
- "maxNotional": 2500000.0,
+ "minNotional": 3000000.0,
+ "maxNotional": 3750000.0,
"maintenanceMarginRate": 0.125,
"maxLeverage": 4.0,
"info": {
"bracket": "7",
"initialLeverage": "4",
- "notionalCap": "2500000",
- "notionalFloor": "2000000",
+ "notionalCap": "3750000",
+ "notionalFloor": "3000000",
"maintMarginRatio": "0.125",
- "cum": "105650.0"
+ "cum": "158450.0"
}
},
{
"tier": 8.0,
"currency": "USDT",
- "minNotional": 2500000.0,
- "maxNotional": 5000000.0,
+ "minNotional": 3750000.0,
+ "maxNotional": 7500000.0,
"maintenanceMarginRate": 0.25,
"maxLeverage": 2.0,
"info": {
"bracket": "8",
"initialLeverage": "2",
- "notionalCap": "5000000",
- "notionalFloor": "2500000",
+ "notionalCap": "7500000",
+ "notionalFloor": "3750000",
"maintMarginRatio": "0.25",
- "cum": "418150.0"
+ "cum": "627200.0"
}
},
{
"tier": 9.0,
"currency": "USDT",
- "minNotional": 5000000.0,
- "maxNotional": 10000000.0,
+ "minNotional": 7500000.0,
+ "maxNotional": 15000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
"bracket": "9",
"initialLeverage": "1",
- "notionalCap": "10000000",
- "notionalFloor": "5000000",
+ "notionalCap": "15000000",
+ "notionalFloor": "7500000",
"maintMarginRatio": "0.5",
- "cum": "1668150.0"
+ "cum": "2502200.0"
}
}
],
@@ -35225,6 +36371,152 @@
}
}
],
+ "SAFE/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "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,
+ "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,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 30000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "30000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 30000.0,
+ "maxNotional": 60000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "60000",
+ "notionalFloor": "30000",
+ "maintMarginRatio": "0.025",
+ "cum": "225.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 60000.0,
+ "maxNotional": 300000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "300000",
+ "notionalFloor": "60000",
+ "maintMarginRatio": "0.05",
+ "cum": "1725.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 300000.0,
+ "maxNotional": 600000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "600000",
+ "notionalFloor": "300000",
+ "maintMarginRatio": "0.1",
+ "cum": "16725.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 600000.0,
+ "maxNotional": 750000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "750000",
+ "notionalFloor": "600000",
+ "maintMarginRatio": "0.125",
+ "cum": "31725.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 750000.0,
+ "maxNotional": 1500000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "1500000",
+ "notionalFloor": "750000",
+ "maintMarginRatio": "0.25",
+ "cum": "125475.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 1500000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "3000000",
+ "notionalFloor": "1500000",
+ "maintMarginRatio": "0.5",
+ "cum": "500475.0"
+ }
+ }
+ ],
"SAGA/USDT:USDT": [
{
"tier": 1.0,
@@ -35517,6 +36809,152 @@
}
}
],
+ "SANTOS/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "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,
+ "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,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 30000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "30000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 30000.0,
+ "maxNotional": 60000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "60000",
+ "notionalFloor": "30000",
+ "maintMarginRatio": "0.025",
+ "cum": "225.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 60000.0,
+ "maxNotional": 300000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "300000",
+ "notionalFloor": "60000",
+ "maintMarginRatio": "0.05",
+ "cum": "1725.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 300000.0,
+ "maxNotional": 600000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "600000",
+ "notionalFloor": "300000",
+ "maintMarginRatio": "0.1",
+ "cum": "16725.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 600000.0,
+ "maxNotional": 750000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "750000",
+ "notionalFloor": "600000",
+ "maintMarginRatio": "0.125",
+ "cum": "31725.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 750000.0,
+ "maxNotional": 1500000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "1500000",
+ "notionalFloor": "750000",
+ "maintMarginRatio": "0.25",
+ "cum": "125475.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 1500000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "3000000",
+ "notionalFloor": "1500000",
+ "maintMarginRatio": "0.5",
+ "cum": "500475.0"
+ }
+ }
+ ],
"SC/USDT:USDT": [
{
"tier": 1.0,
@@ -36011,14 +37449,14 @@
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 20.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "20",
+ "initialLeverage": "75",
"notionalCap": "5000",
"notionalFloor": "0",
- "maintMarginRatio": "0.02",
+ "maintMarginRatio": "0.01",
"cum": "0.0"
}
},
@@ -36026,80 +37464,128 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 10.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "10",
- "notionalCap": "25000",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
"notionalFloor": "5000",
- "maintMarginRatio": "0.025",
+ "maintMarginRatio": "0.015",
"cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 100000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 8.0,
+ "minNotional": 10000.0,
+ "maxNotional": 30000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "8",
- "notionalCap": "100000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "650.0"
+ "initialLeverage": "25",
+ "notionalCap": "30000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 100000.0,
- "maxNotional": 250000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 30000.0,
+ "maxNotional": 60000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "250000",
- "notionalFloor": "100000",
- "maintMarginRatio": "0.1",
- "cum": "5650.0"
+ "initialLeverage": "20",
+ "notionalCap": "60000",
+ "notionalFloor": "30000",
+ "maintMarginRatio": "0.025",
+ "cum": "225.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 250000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 2.0,
+ "minNotional": 60000.0,
+ "maxNotional": 300000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "2",
- "notionalCap": "1000000",
- "notionalFloor": "250000",
- "maintMarginRatio": "0.125",
- "cum": "11900.0"
+ "initialLeverage": "10",
+ "notionalCap": "300000",
+ "notionalFloor": "60000",
+ "maintMarginRatio": "0.05",
+ "cum": "1725.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
+ "minNotional": 300000.0,
+ "maxNotional": 600000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "600000",
+ "notionalFloor": "300000",
+ "maintMarginRatio": "0.1",
+ "cum": "16725.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 600000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "1000000",
+ "notionalFloor": "600000",
+ "maintMarginRatio": "0.125",
+ "cum": "31725.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
"minNotional": 1000000.0,
+ "maxNotional": 1500000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "1500000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.25",
+ "cum": "156725.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 1500000.0,
"maxNotional": 3000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "6",
+ "bracket": "9",
"initialLeverage": "1",
"notionalCap": "3000000",
- "notionalFloor": "1000000",
+ "notionalFloor": "1500000",
"maintMarginRatio": "0.5",
- "cum": "386900.0"
+ "cum": "531725.0"
}
}
],
@@ -36921,14 +38407,14 @@
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 20.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "20",
+ "initialLeverage": "75",
"notionalCap": "5000",
"notionalFloor": "0",
- "maintMarginRatio": "0.02",
+ "maintMarginRatio": "0.01",
"cum": "0.0"
}
},
@@ -36936,96 +38422,128 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 15.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "15",
- "notionalCap": "25000",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
"notionalFloor": "5000",
- "maintMarginRatio": "0.025",
+ "maintMarginRatio": "0.015",
"cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 200000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 10000.0,
+ "maxNotional": 50000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "200000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "650.0"
+ "initialLeverage": "25",
+ "notionalCap": "50000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 200000.0,
- "maxNotional": 500000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 50000.0,
+ "maxNotional": 100000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "500000",
- "notionalFloor": "200000",
- "maintMarginRatio": "0.1",
- "cum": "10650.0"
+ "initialLeverage": "20",
+ "notionalCap": "100000",
+ "notionalFloor": "50000",
+ "maintMarginRatio": "0.025",
+ "cum": "325.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 500000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 100000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "4",
- "notionalCap": "1000000",
- "notionalFloor": "500000",
- "maintMarginRatio": "0.125",
- "cum": "23150.0"
+ "initialLeverage": "10",
+ "notionalCap": "500000",
+ "notionalFloor": "100000",
+ "maintMarginRatio": "0.05",
+ "cum": "2825.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 3000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "2",
- "notionalCap": "3000000",
- "notionalFloor": "1000000",
- "maintMarginRatio": "0.25",
- "cum": "148150.0"
+ "initialLeverage": "5",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.1",
+ "cum": "27825.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 1250000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "1250000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.125",
+ "cum": "52825.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1250000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "3000000",
+ "notionalFloor": "1250000",
+ "maintMarginRatio": "0.25",
+ "cum": "209075.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
"minNotional": 3000000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "7",
+ "bracket": "9",
"initialLeverage": "1",
"notionalCap": "5000000",
"notionalFloor": "3000000",
"maintMarginRatio": "0.5",
- "cum": "898150.0"
+ "cum": "959075.0"
}
}
],
@@ -40377,20 +41895,20 @@
}
}
],
- "TRU/USDT:USDT": [
+ "TROY/USDT:USDT": [
{
"tier": 1.0,
"currency": "USDT",
"minNotional": 0.0,
"maxNotional": 5000.0,
- "maintenanceMarginRate": 0.02,
- "maxLeverage": 20.0,
+ "maintenanceMarginRate": 0.01,
+ "maxLeverage": 75.0,
"info": {
"bracket": "1",
- "initialLeverage": "20",
+ "initialLeverage": "75",
"notionalCap": "5000",
"notionalFloor": "0",
- "maintMarginRatio": "0.02",
+ "maintMarginRatio": "0.01",
"cum": "0.0"
}
},
@@ -40398,96 +41916,274 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 15.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "15",
- "notionalCap": "25000",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
"notionalFloor": "5000",
- "maintMarginRatio": "0.025",
+ "maintMarginRatio": "0.015",
"cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 200000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 10000.0,
+ "maxNotional": 30000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "200000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "650.0"
+ "initialLeverage": "25",
+ "notionalCap": "30000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 200000.0,
- "maxNotional": 500000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 30000.0,
+ "maxNotional": 60000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "500000",
- "notionalFloor": "200000",
- "maintMarginRatio": "0.1",
- "cum": "10650.0"
+ "initialLeverage": "20",
+ "notionalCap": "60000",
+ "notionalFloor": "30000",
+ "maintMarginRatio": "0.025",
+ "cum": "225.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 500000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 60000.0,
+ "maxNotional": 300000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "4",
- "notionalCap": "1000000",
- "notionalFloor": "500000",
- "maintMarginRatio": "0.125",
- "cum": "23150.0"
+ "initialLeverage": "10",
+ "notionalCap": "300000",
+ "notionalFloor": "60000",
+ "maintMarginRatio": "0.05",
+ "cum": "1725.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 3000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 300000.0,
+ "maxNotional": 600000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "2",
- "notionalCap": "3000000",
- "notionalFloor": "1000000",
- "maintMarginRatio": "0.25",
- "cum": "148150.0"
+ "initialLeverage": "5",
+ "notionalCap": "600000",
+ "notionalFloor": "300000",
+ "maintMarginRatio": "0.1",
+ "cum": "16725.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
+ "minNotional": 600000.0,
+ "maxNotional": 750000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "750000",
+ "notionalFloor": "600000",
+ "maintMarginRatio": "0.125",
+ "cum": "31725.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 750000.0,
+ "maxNotional": 1500000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "1500000",
+ "notionalFloor": "750000",
+ "maintMarginRatio": "0.25",
+ "cum": "125475.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
+ "minNotional": 1500000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.5,
+ "maxLeverage": 1.0,
+ "info": {
+ "bracket": "9",
+ "initialLeverage": "1",
+ "notionalCap": "3000000",
+ "notionalFloor": "1500000",
+ "maintMarginRatio": "0.5",
+ "cum": "500475.0"
+ }
+ }
+ ],
+ "TRU/USDT:USDT": [
+ {
+ "tier": 1.0,
+ "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,
+ "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,
+ "currency": "USDT",
+ "minNotional": 10000.0,
+ "maxNotional": 50000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
+ "info": {
+ "bracket": "3",
+ "initialLeverage": "25",
+ "notionalCap": "50000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
+ }
+ },
+ {
+ "tier": 4.0,
+ "currency": "USDT",
+ "minNotional": 50000.0,
+ "maxNotional": 100000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
+ "info": {
+ "bracket": "4",
+ "initialLeverage": "20",
+ "notionalCap": "100000",
+ "notionalFloor": "50000",
+ "maintMarginRatio": "0.025",
+ "cum": "325.0"
+ }
+ },
+ {
+ "tier": 5.0,
+ "currency": "USDT",
+ "minNotional": 100000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
+ "info": {
+ "bracket": "5",
+ "initialLeverage": "10",
+ "notionalCap": "500000",
+ "notionalFloor": "100000",
+ "maintMarginRatio": "0.05",
+ "cum": "2825.0"
+ }
+ },
+ {
+ "tier": 6.0,
+ "currency": "USDT",
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
+ "info": {
+ "bracket": "6",
+ "initialLeverage": "5",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.1",
+ "cum": "27825.0"
+ }
+ },
+ {
+ "tier": 7.0,
+ "currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 1250000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "1250000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.125",
+ "cum": "52825.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1250000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "3000000",
+ "notionalFloor": "1250000",
+ "maintMarginRatio": "0.25",
+ "cum": "209075.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
"minNotional": 3000000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "7",
+ "bracket": "9",
"initialLeverage": "1",
"notionalCap": "5000000",
"notionalFloor": "3000000",
"maintMarginRatio": "0.5",
- "cum": "898150.0"
+ "cum": "959075.0"
}
}
],
@@ -40919,14 +42615,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"
}
},
@@ -40934,96 +42630,128 @@
"tier": 2.0,
"currency": "USDT",
"minNotional": 5000.0,
- "maxNotional": 25000.0,
- "maintenanceMarginRate": 0.025,
- "maxLeverage": 20.0,
+ "maxNotional": 10000.0,
+ "maintenanceMarginRate": 0.015,
+ "maxLeverage": 50.0,
"info": {
"bracket": "2",
- "initialLeverage": "20",
- "notionalCap": "25000",
+ "initialLeverage": "50",
+ "notionalCap": "10000",
"notionalFloor": "5000",
- "maintMarginRatio": "0.025",
- "cum": "50.0"
+ "maintMarginRatio": "0.015",
+ "cum": "25.0"
}
},
{
"tier": 3.0,
"currency": "USDT",
- "minNotional": 25000.0,
- "maxNotional": 200000.0,
- "maintenanceMarginRate": 0.05,
- "maxLeverage": 10.0,
+ "minNotional": 10000.0,
+ "maxNotional": 50000.0,
+ "maintenanceMarginRate": 0.02,
+ "maxLeverage": 25.0,
"info": {
"bracket": "3",
- "initialLeverage": "10",
- "notionalCap": "200000",
- "notionalFloor": "25000",
- "maintMarginRatio": "0.05",
- "cum": "675.0"
+ "initialLeverage": "25",
+ "notionalCap": "50000",
+ "notionalFloor": "10000",
+ "maintMarginRatio": "0.02",
+ "cum": "75.0"
}
},
{
"tier": 4.0,
"currency": "USDT",
- "minNotional": 200000.0,
- "maxNotional": 500000.0,
- "maintenanceMarginRate": 0.1,
- "maxLeverage": 5.0,
+ "minNotional": 50000.0,
+ "maxNotional": 100000.0,
+ "maintenanceMarginRate": 0.025,
+ "maxLeverage": 20.0,
"info": {
"bracket": "4",
- "initialLeverage": "5",
- "notionalCap": "500000",
- "notionalFloor": "200000",
- "maintMarginRatio": "0.1",
- "cum": "10675.0"
+ "initialLeverage": "20",
+ "notionalCap": "100000",
+ "notionalFloor": "50000",
+ "maintMarginRatio": "0.025",
+ "cum": "325.0"
}
},
{
"tier": 5.0,
"currency": "USDT",
- "minNotional": 500000.0,
- "maxNotional": 1000000.0,
- "maintenanceMarginRate": 0.125,
- "maxLeverage": 4.0,
+ "minNotional": 100000.0,
+ "maxNotional": 500000.0,
+ "maintenanceMarginRate": 0.05,
+ "maxLeverage": 10.0,
"info": {
"bracket": "5",
- "initialLeverage": "4",
- "notionalCap": "1000000",
- "notionalFloor": "500000",
- "maintMarginRatio": "0.125",
- "cum": "23175.0"
+ "initialLeverage": "10",
+ "notionalCap": "500000",
+ "notionalFloor": "100000",
+ "maintMarginRatio": "0.05",
+ "cum": "2825.0"
}
},
{
"tier": 6.0,
"currency": "USDT",
- "minNotional": 1000000.0,
- "maxNotional": 3000000.0,
- "maintenanceMarginRate": 0.25,
- "maxLeverage": 2.0,
+ "minNotional": 500000.0,
+ "maxNotional": 1000000.0,
+ "maintenanceMarginRate": 0.1,
+ "maxLeverage": 5.0,
"info": {
"bracket": "6",
- "initialLeverage": "2",
- "notionalCap": "3000000",
- "notionalFloor": "1000000",
- "maintMarginRatio": "0.25",
- "cum": "148175.0"
+ "initialLeverage": "5",
+ "notionalCap": "1000000",
+ "notionalFloor": "500000",
+ "maintMarginRatio": "0.1",
+ "cum": "27825.0"
}
},
{
"tier": 7.0,
"currency": "USDT",
+ "minNotional": 1000000.0,
+ "maxNotional": 1250000.0,
+ "maintenanceMarginRate": 0.125,
+ "maxLeverage": 4.0,
+ "info": {
+ "bracket": "7",
+ "initialLeverage": "4",
+ "notionalCap": "1250000",
+ "notionalFloor": "1000000",
+ "maintMarginRatio": "0.125",
+ "cum": "52825.0"
+ }
+ },
+ {
+ "tier": 8.0,
+ "currency": "USDT",
+ "minNotional": 1250000.0,
+ "maxNotional": 3000000.0,
+ "maintenanceMarginRate": 0.25,
+ "maxLeverage": 2.0,
+ "info": {
+ "bracket": "8",
+ "initialLeverage": "2",
+ "notionalCap": "3000000",
+ "notionalFloor": "1250000",
+ "maintMarginRatio": "0.25",
+ "cum": "209075.0"
+ }
+ },
+ {
+ "tier": 9.0,
+ "currency": "USDT",
"minNotional": 3000000.0,
"maxNotional": 5000000.0,
"maintenanceMarginRate": 0.5,
"maxLeverage": 1.0,
"info": {
- "bracket": "7",
+ "bracket": "9",
"initialLeverage": "1",
"notionalCap": "5000000",
"notionalFloor": "3000000",
"maintMarginRatio": "0.5",
- "cum": "898175.0"
+ "cum": "959075.0"
}
}
],
diff --git a/freqtrade/exchange/bitpanda.py b/freqtrade/exchange/bitpanda.py
index cca961cb7..05c5af6ac 100644
--- a/freqtrade/exchange/bitpanda.py
+++ b/freqtrade/exchange/bitpanda.py
@@ -2,7 +2,6 @@
import logging
from datetime import datetime, timezone
-from typing import Optional
from freqtrade.exchange import Exchange
@@ -17,7 +16,7 @@ class Bitpanda(Exchange):
"""
def get_trades_for_order(
- self, order_id: str, pair: str, since: datetime, params: Optional[dict] = None
+ self, order_id: str, pair: str, since: datetime, params: dict | None = None
) -> list:
"""
Fetch Orders using the "fetch_my_trades" endpoint and filter them by order-id.
diff --git a/freqtrade/exchange/bybit.py b/freqtrade/exchange/bybit.py
index 9c70cf7a0..d143a7543 100644
--- a/freqtrade/exchange/bybit.py
+++ b/freqtrade/exchange/bybit.py
@@ -2,7 +2,7 @@
import logging
from datetime import datetime, timedelta
-from typing import Any, Optional
+from typing import Any
import ccxt
@@ -11,7 +11,7 @@ from freqtrade.enums import CandleType, MarginMode, PriceType, TradingMode
from freqtrade.exceptions import DDosProtection, ExchangeError, OperationalException, TemporaryError
from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier
-from freqtrade.exchange.exchange_types import FtHas
+from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
from freqtrade.util.datetime_helpers import dt_now, dt_ts
@@ -115,9 +115,9 @@ class Bybit(Exchange):
raise OperationalException(e) from e
def ohlcv_candle_limit(
- self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None
+ self, timeframe: str, candle_type: CandleType, since_ms: int | None = None
) -> int:
- if candle_type in (CandleType.FUNDING_RATE):
+ if candle_type == CandleType.FUNDING_RATE:
return 200
return super().ohlcv_candle_limit(timeframe, candle_type, since_ms)
@@ -157,7 +157,7 @@ class Bybit(Exchange):
leverage: float,
wallet_balance: float, # Or margin balance
open_trades: list,
- ) -> Optional[float]:
+ ) -> float | None:
"""
Important: Must be fetching data from cached values as this is used by backtesting!
PERPETUAL:
@@ -229,7 +229,9 @@ class Bybit(Exchange):
logger.warning(f"Could not update funding fees for {pair}.")
return 0.0
- def fetch_orders(self, pair: str, since: datetime, params: Optional[dict] = None) -> list[dict]:
+ def fetch_orders(
+ self, pair: str, since: datetime, params: dict | None = None
+ ) -> list[CcxtOrder]:
"""
Fetch all orders for a pair "since"
:param pair: Pair for the query
@@ -246,7 +248,7 @@ class Bybit(Exchange):
return orders
- def fetch_order(self, order_id: str, pair: str, params: Optional[dict] = None) -> dict:
+ def fetch_order(self, order_id: str, pair: str, params: dict | None = None) -> CcxtOrder:
if self.exchange_has("fetchOrder"):
# Set acknowledged to True to avoid ccxt exception
params = {"acknowledged": True}
diff --git a/freqtrade/exchange/common.py b/freqtrade/exchange/common.py
index b32216b05..cd451062f 100644
--- a/freqtrade/exchange/common.py
+++ b/freqtrade/exchange/common.py
@@ -1,8 +1,9 @@
import asyncio
import logging
import time
+from collections.abc import Callable
from functools import wraps
-from typing import Any, Callable, Optional, TypeVar, cast, overload
+from typing import Any, TypeVar, cast, overload
from freqtrade.constants import ExchangeConfig
from freqtrade.exceptions import DDosProtection, RetryableOrderError, TemporaryError
@@ -172,7 +173,7 @@ def retrier(_func: F, *, retries=API_RETRY_COUNT) -> F: ...
def retrier(*, retries=API_RETRY_COUNT) -> Callable[[F], F]: ...
-def retrier(_func: Optional[F] = None, *, retries=API_RETRY_COUNT):
+def retrier(_func: F | None = None, *, retries=API_RETRY_COUNT):
def decorator(f: F) -> F:
@wraps(f)
def wrapper(*args, **kwargs):
@@ -185,7 +186,7 @@ def retrier(_func: Optional[F] = None, *, retries=API_RETRY_COUNT):
logger.warning(msg + f"Retrying still for {count} times.")
count -= 1
kwargs.update({"count": count})
- if isinstance(ex, (DDosProtection, RetryableOrderError)):
+ if isinstance(ex, DDosProtection | RetryableOrderError):
# increasing backoff
backoff_delay = calculate_backoff(count + 1, retries)
logger.info(f"Applying DDosProtection backoff delay: {backoff_delay}")
diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py
index f7fb8a9c7..1c1d3bf7f 100644
--- a/freqtrade/exchange/exchange.py
+++ b/freqtrade/exchange/exchange.py
@@ -12,7 +12,7 @@ from copy import deepcopy
from datetime import datetime, timedelta, timezone
from math import floor, isnan
from threading import Lock
-from typing import Any, Literal, Optional, Union
+from typing import Any, Literal, TypeGuard
import ccxt
import ccxt.pro as ccxt_pro
@@ -70,6 +70,7 @@ from freqtrade.exchange.common import (
)
from freqtrade.exchange.exchange_types import (
CcxtBalances,
+ CcxtOrder,
CcxtPosition,
FtHas,
OHLCVResponse,
@@ -169,7 +170,7 @@ class Exchange:
self,
config: Config,
*,
- exchange_config: Optional[ExchangeConfig] = None,
+ exchange_config: ExchangeConfig | None = None,
validate: bool = True,
load_leverage_tiers: bool = False,
) -> None:
@@ -181,7 +182,7 @@ class Exchange:
self._api: ccxt.Exchange
self._api_async: ccxt_pro.Exchange
self._ws_async: ccxt_pro.Exchange = None
- self._exchange_ws: Optional[ExchangeWS] = None
+ self._exchange_ws: ExchangeWS | None = None
self._markets: dict = {}
self._trading_fees: dict[str, Any] = {}
self._leverage_tiers: dict[str, list[dict]] = {}
@@ -378,7 +379,7 @@ class Exchange:
logger.info("Applying additional ccxt config: %s", ccxt_kwargs)
if self._ccxt_params:
# Inject static options after the above output to not confuse users.
- ccxt_kwargs = deep_merge_dicts(self._ccxt_params, ccxt_kwargs)
+ ccxt_kwargs = deep_merge_dicts(self._ccxt_params, deepcopy(ccxt_kwargs))
if ccxt_kwargs:
ex_config.update(ccxt_kwargs)
try:
@@ -452,7 +453,7 @@ class Exchange:
logger.info(f"API {endpoint}: {add_info_str}{response}")
def ohlcv_candle_limit(
- self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None
+ self, timeframe: str, candle_type: CandleType, since_ms: int | None = None
) -> int:
"""
Exchange ohlcv candle limit
@@ -472,8 +473,8 @@ class Exchange:
def get_markets(
self,
- base_currencies: Optional[list[str]] = None,
- quote_currencies: Optional[list[str]] = None,
+ base_currencies: list[str] | None = None,
+ quote_currencies: list[str] | None = None,
spot_only: bool = False,
margin_only: bool = False,
futures_only: bool = False,
@@ -566,7 +567,7 @@ class Exchange:
else:
return DataFrame(columns=DEFAULT_TRADES_COLUMNS)
- def get_contract_size(self, pair: str) -> Optional[float]:
+ def get_contract_size(self, pair: str) -> float | None:
if self.trading_mode == TradingMode.FUTURES:
market = self.markets.get(pair, {})
contract_size: float = 1.0
@@ -587,7 +588,7 @@ class Exchange:
trade["amount"] = trade["amount"] * contract_size
return trades
- def _order_contracts_to_amount(self, order: dict) -> dict:
+ def _order_contracts_to_amount(self, order: CcxtOrder) -> CcxtOrder:
if "symbol" in order and order["symbol"] is not None:
contract_size = self.get_contract_size(order["symbol"])
if contract_size != 1:
@@ -709,7 +710,7 @@ class Exchange:
return pair
raise ValueError(f"Could not combine {curr_1} and {curr_2} to get a valid pair.")
- def validate_timeframes(self, timeframe: Optional[str]) -> None:
+ def validate_timeframes(self, timeframe: str | None) -> None:
"""
Check if timeframe from config is a supported timeframe on the exchange
"""
@@ -839,7 +840,7 @@ class Exchange:
def validate_trading_mode_and_margin_mode(
self,
trading_mode: TradingMode,
- margin_mode: Optional[MarginMode], # Only None when trading_mode = TradingMode.SPOT
+ margin_mode: MarginMode | None, # Only None when trading_mode = TradingMode.SPOT
):
"""
Checks if freqtrade can perform trades using the configured
@@ -855,7 +856,7 @@ class Exchange:
f"Freqtrade does not support {mm_value} {trading_mode} on {self.name}"
)
- def get_option(self, param: str, default: Optional[Any] = None) -> Any:
+ def get_option(self, param: str, default: Any | None = None) -> Any:
"""
Get parameter value from _ft_has
"""
@@ -872,7 +873,7 @@ class Exchange:
return self._ft_has["exchange_has_overrides"][endpoint]
return endpoint in self._api_async.has and self._api_async.has[endpoint]
- def get_precision_amount(self, pair: str) -> Optional[float]:
+ def get_precision_amount(self, pair: str) -> float | None:
"""
Returns the amount precision of the exchange.
:param pair: Pair to get precision for
@@ -880,7 +881,7 @@ class Exchange:
"""
return self.markets.get(pair, {}).get("precision", {}).get("amount", None)
- def get_precision_price(self, pair: str) -> Optional[float]:
+ def get_precision_price(self, pair: str) -> float | None:
"""
Returns the price precision of the exchange.
:param pair: Pair to get precision for
@@ -920,8 +921,8 @@ class Exchange:
return 1 / pow(10, precision)
def get_min_pair_stake_amount(
- self, pair: str, price: float, stoploss: float, leverage: Optional[float] = 1.0
- ) -> Optional[float]:
+ self, pair: str, price: float, stoploss: float, leverage: float | None = 1.0
+ ) -> float | None:
return self._get_stake_amount_limit(pair, price, stoploss, "min", leverage)
def get_max_pair_stake_amount(self, pair: str, price: float, leverage: float = 1.0) -> float:
@@ -939,8 +940,8 @@ class Exchange:
price: float,
stoploss: float,
limit: Literal["min", "max"],
- leverage: Optional[float] = 1.0,
- ) -> Optional[float]:
+ leverage: float | None = 1.0,
+ ) -> float | None:
isMin = limit == "min"
try:
@@ -997,20 +998,20 @@ class Exchange:
self,
pair: str,
ordertype: str,
- side: str,
+ side: BuySell,
amount: float,
rate: float,
leverage: float,
- params: Optional[dict] = None,
+ params: dict | None = None,
stop_loss: bool = False,
- ) -> dict[str, Any]:
+ ) -> CcxtOrder:
now = dt_now()
order_id = f"dry_run_{side}_{pair}_{now.timestamp()}"
# Rounding here must respect to contract sizes
_amount = self._contracts_to_amount(
pair, self.amount_to_precision(pair, self._amount_to_contracts(pair, amount))
)
- dry_order: dict[str, Any] = {
+ dry_order: CcxtOrder = {
"id": order_id,
"symbol": pair,
"price": rate,
@@ -1026,14 +1027,13 @@ class Exchange:
"status": "open",
"fee": None,
"info": {},
- "leverage": leverage,
}
if stop_loss:
dry_order["info"] = {"stopPrice": dry_order["price"]}
dry_order[self._ft_has["stop_price_prop"]] = dry_order["price"]
# Workaround to avoid filling stoploss orders immediately
dry_order["ft_order_type"] = "stoploss"
- orderbook: Optional[OrderBook] = None
+ orderbook: OrderBook | None = None
if self.exchange_has("fetchL2OrderBook"):
orderbook = self.fetch_l2_order_book(pair, 20)
if ordertype == "limit" and orderbook:
@@ -1055,7 +1055,7 @@ class Exchange:
"filled": _amount,
"remaining": 0.0,
"status": "closed",
- "cost": (dry_order["amount"] * average),
+ "cost": (_amount * average),
}
)
# market orders will always incurr taker fees
@@ -1072,9 +1072,9 @@ class Exchange:
def add_dry_order_fee(
self,
pair: str,
- dry_order: dict[str, Any],
+ dry_order: CcxtOrder,
taker_or_maker: MakerTaker,
- ) -> dict[str, Any]:
+ ) -> CcxtOrder:
fee = self.get_fee(pair, taker_or_maker=taker_or_maker)
dry_order.update(
{
@@ -1088,7 +1088,7 @@ class Exchange:
return dry_order
def get_dry_market_fill_price(
- self, pair: str, side: str, amount: float, rate: float, orderbook: Optional[OrderBook]
+ self, pair: str, side: str, amount: float, rate: float, orderbook: OrderBook | None
) -> float:
"""
Get the market order fill price based on orderbook interpolation
@@ -1136,7 +1136,7 @@ class Exchange:
pair: str,
side: str,
limit: float,
- orderbook: Optional[OrderBook] = None,
+ orderbook: OrderBook | None = None,
offset: float = 0.0,
) -> bool:
if not self.exchange_has("fetchL2OrderBook"):
@@ -1158,8 +1158,8 @@ class Exchange:
return False
def check_dry_limit_order_filled(
- self, order: dict[str, Any], immediate: bool = False, orderbook: Optional[OrderBook] = None
- ) -> dict[str, Any]:
+ self, order: CcxtOrder, immediate: bool = False, orderbook: OrderBook | None = None
+ ) -> CcxtOrder:
"""
Check dry-run limit order fill and update fee (if it filled).
"""
@@ -1186,7 +1186,7 @@ class Exchange:
return order
- def fetch_dry_run_order(self, order_id) -> dict[str, Any]:
+ def fetch_dry_run_order(self, order_id) -> CcxtOrder:
"""
Return dry-run order
Only call if running in dry-run mode.
@@ -1230,10 +1230,10 @@ class Exchange:
params.update({"reduceOnly": True})
return params
- def _order_needs_price(self, ordertype: str) -> bool:
+ def _order_needs_price(self, side: BuySell, ordertype: str) -> bool:
return (
ordertype != "market"
- or self._api.options.get("createMarketBuyOrderRequiresPrice", False)
+ or (side == "buy" and self._api.options.get("createMarketBuyOrderRequiresPrice", False))
or self._ft_has.get("marketOrderRequiresPrice", False)
)
@@ -1248,7 +1248,7 @@ class Exchange:
leverage: float,
reduceOnly: bool = False,
time_in_force: str = "GTC",
- ) -> dict:
+ ) -> CcxtOrder:
if self._config["dry_run"]:
dry_order = self.create_dry_run_order(
pair, ordertype, side, amount, self.price_to_precision(pair, rate), leverage
@@ -1260,7 +1260,7 @@ class Exchange:
try:
# Set the precision for amount and price(rate) as accepted by the exchange
amount = self.amount_to_precision(pair, self._amount_to_contracts(pair, amount))
- needs_price = self._order_needs_price(ordertype)
+ needs_price = self._order_needs_price(side, ordertype)
rate_for_order = self.price_to_precision(pair, rate) if needs_price else None
if not reduceOnly:
@@ -1306,7 +1306,7 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
- def stoploss_adjust(self, stop_loss: float, order: dict, side: str) -> bool:
+ def stoploss_adjust(self, stop_loss: float, order: CcxtOrder, side: str) -> bool:
"""
Verify stop_loss against stoploss-order value (limit or price)
Returns True if adjustment is necessary.
@@ -1367,7 +1367,7 @@ class Exchange:
order_types: dict,
side: BuySell,
leverage: float,
- ) -> dict:
+ ) -> CcxtOrder:
"""
creates a stoploss order.
requires `_ft_has['stoploss_order_types']` to be set as a dict mapping limit and market
@@ -1460,7 +1460,7 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
- def fetch_order_emulated(self, order_id: str, pair: str, params: dict) -> dict:
+ def fetch_order_emulated(self, order_id: str, pair: str, params: dict) -> CcxtOrder:
"""
Emulated fetch_order if the exchange doesn't support fetch_order, but requires separate
calls for open and closed orders.
@@ -1494,7 +1494,7 @@ class Exchange:
raise OperationalException(e) from e
@retrier(retries=API_FETCH_ORDER_RETRY_COUNT)
- def fetch_order(self, order_id: str, pair: str, params: Optional[dict] = None) -> dict:
+ def fetch_order(self, order_id: str, pair: str, params: dict | None = None) -> CcxtOrder:
if self._config["dry_run"]:
return self.fetch_dry_run_order(order_id)
if params is None:
@@ -1523,12 +1523,14 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
- def fetch_stoploss_order(self, order_id: str, pair: str, params: Optional[dict] = None) -> dict:
+ def fetch_stoploss_order(
+ self, order_id: str, pair: str, params: dict | None = None
+ ) -> CcxtOrder:
return self.fetch_order(order_id, pair, params)
def fetch_order_or_stoploss_order(
self, order_id: str, pair: str, stoploss_order: bool = False
- ) -> dict:
+ ) -> CcxtOrder:
"""
Simple wrapper calling either fetch_order or fetch_stoploss_order depending on
the stoploss_order parameter
@@ -1540,7 +1542,7 @@ class Exchange:
return self.fetch_stoploss_order(order_id, pair)
return self.fetch_order(order_id, pair)
- def check_order_canceled_empty(self, order: dict) -> bool:
+ def check_order_canceled_empty(self, order: CcxtOrder) -> bool:
"""
Verify if an order has been cancelled without being partially filled
:param order: Order dict as returned from fetch_order()
@@ -1549,7 +1551,7 @@ class Exchange:
return order.get("status") in NON_OPEN_EXCHANGE_STATES and order.get("filled") == 0.0
@retrier
- def cancel_order(self, order_id: str, pair: str, params: Optional[dict] = None) -> dict:
+ def cancel_order(self, order_id: str, pair: str, params: dict | None = None) -> dict[str, Any]:
if self._config["dry_run"]:
try:
order = self.fetch_dry_run_order(order_id)
@@ -1577,19 +1579,17 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
- def cancel_stoploss_order(
- self, order_id: str, pair: str, params: Optional[dict] = None
- ) -> dict:
+ def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict:
return self.cancel_order(order_id, pair, params)
- def is_cancel_order_result_suitable(self, corder) -> bool:
+ def is_cancel_order_result_suitable(self, corder) -> TypeGuard[CcxtOrder]:
if not isinstance(corder, dict):
return False
required = ("fee", "status", "amount")
return all(corder.get(k, None) is not None for k in required)
- def cancel_order_with_result(self, order_id: str, pair: str, amount: float) -> dict:
+ def cancel_order_with_result(self, order_id: str, pair: str, amount: float) -> CcxtOrder:
"""
Cancel order returning a result.
Creates a fake result if cancel order returns a non-usable result
@@ -1620,7 +1620,9 @@ class Exchange:
return order
- def cancel_stoploss_order_with_result(self, order_id: str, pair: str, amount: float) -> dict:
+ def cancel_stoploss_order_with_result(
+ self, order_id: str, pair: str, amount: float
+ ) -> CcxtOrder:
"""
Cancel stoploss order returning a result.
Creates a fake result if cancel order returns a non-usable result
@@ -1662,7 +1664,7 @@ class Exchange:
raise OperationalException(e) from e
@retrier
- def fetch_positions(self, pair: Optional[str] = None) -> list[CcxtPosition]:
+ def fetch_positions(self, pair: str | None = None) -> list[CcxtPosition]:
"""
Fetch positions from the exchange.
If no pair is given, all positions are returned.
@@ -1686,7 +1688,7 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
- def _fetch_orders_emulate(self, pair: str, since_ms: int) -> list[dict]:
+ def _fetch_orders_emulate(self, pair: str, since_ms: int) -> list[CcxtOrder]:
orders = []
if self.exchange_has("fetchClosedOrders"):
orders = self._api.fetch_closed_orders(pair, since=since_ms)
@@ -1696,7 +1698,9 @@ class Exchange:
return orders
@retrier(retries=0)
- def fetch_orders(self, pair: str, since: datetime, params: Optional[dict] = None) -> list[dict]:
+ def fetch_orders(
+ self, pair: str, since: datetime, params: dict | None = None
+ ) -> list[CcxtOrder]:
"""
Fetch all orders for a pair "since"
:param pair: Pair for the query
@@ -1712,7 +1716,9 @@ class Exchange:
if not params:
params = {}
try:
- orders: list[dict] = self._api.fetch_orders(pair, since=since_ms, params=params)
+ orders: list[CcxtOrder] = self._api.fetch_orders(
+ pair, since=since_ms, params=params
+ )
except ccxt.NotSupported:
# Some exchanges don't support fetchOrders
# attempt to fetch open and closed orders separately
@@ -1757,7 +1763,7 @@ class Exchange:
raise OperationalException(e) from e
@retrier
- def fetch_bids_asks(self, symbols: Optional[list[str]] = None, cached: bool = False) -> dict:
+ def fetch_bids_asks(self, symbols: list[str] | None = None, cached: bool = False) -> dict:
"""
:param symbols: List of symbols to fetch
:param cached: Allow cached result
@@ -1790,7 +1796,7 @@ class Exchange:
raise OperationalException(e) from e
@retrier
- def get_tickers(self, symbols: Optional[list[str]] = None, cached: bool = False) -> Tickers:
+ def get_tickers(self, symbols: list[str] | None = None, cached: bool = False) -> Tickers:
"""
:param cached: Allow cached result
:return: fetch_tickers result
@@ -1850,7 +1856,7 @@ class Exchange:
@staticmethod
def get_next_limit_in_list(
- limit: int, limit_range: Optional[list[int]], range_required: bool = True
+ limit: int, limit_range: list[int] | None, range_required: bool = True
):
"""
Get next greater value in the list.
@@ -1914,8 +1920,8 @@ class Exchange:
refresh: bool,
side: EntryExit,
is_short: bool,
- order_book: Optional[OrderBook] = None,
- ticker: Optional[Ticker] = None,
+ order_book: OrderBook | None = None,
+ ticker: Ticker | None = None,
) -> float:
"""
Calculates bid/ask target
@@ -1964,7 +1970,7 @@ class Exchange:
def _get_rate_from_ticker(
self, side: EntryExit, ticker: Ticker, conf_strategy: dict[str, Any], price_side: BidAsk
- ) -> Optional[float]:
+ ) -> float | None:
"""
Get rate from ticker.
"""
@@ -2043,7 +2049,7 @@ class Exchange:
@retrier
def get_trades_for_order(
- self, order_id: str, pair: str, since: datetime, params: Optional[dict] = None
+ self, order_id: str, pair: str, since: datetime, params: dict | None = None
) -> list:
"""
Fetch Orders using the "fetch_my_trades" endpoint and filter them by order-id.
@@ -2090,7 +2096,7 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
- def get_order_id_conditional(self, order: dict[str, Any]) -> str:
+ def get_order_id_conditional(self, order: CcxtOrder) -> str:
return order["id"]
@retrier
@@ -2139,7 +2145,7 @@ class Exchange:
raise OperationalException(e) from e
@staticmethod
- def order_has_fee(order: dict) -> bool:
+ def order_has_fee(order: CcxtOrder) -> bool:
"""
Verifies if the passed in order dict has the needed keys to extract fees,
and that these keys (currency, cost) are not empty.
@@ -2158,7 +2164,7 @@ class Exchange:
def calculate_fee_rate(
self, fee: dict, symbol: str, cost: float, amount: float
- ) -> Optional[float]:
+ ) -> float | None:
"""
Calculate fee rate if it's not given by the exchange.
:param fee: ccxt Fee dict - must contain cost / currency / rate
@@ -2197,8 +2203,8 @@ class Exchange:
return round((fee_cost * fee_to_quote_rate) / cost, 8)
def extract_cost_curr_rate(
- self, fee: dict, symbol: str, cost: float, amount: float
- ) -> tuple[float, str, Optional[float]]:
+ self, fee: dict[str, Any], symbol: str, cost: float, amount: float
+ ) -> tuple[float, str, float | None]:
"""
Extract tuple of cost, currency, rate.
Requires order_has_fee to run first!
@@ -2223,7 +2229,7 @@ class Exchange:
since_ms: int,
candle_type: CandleType,
is_new_pair: bool = False,
- until_ms: Optional[int] = None,
+ until_ms: int | None = None,
) -> DataFrame:
"""
Get candle history using asyncio and returns the list of candles.
@@ -2257,7 +2263,7 @@ class Exchange:
candle_type: CandleType,
is_new_pair: bool = False,
raise_: bool = False,
- until_ms: Optional[int] = None,
+ until_ms: int | None = None,
) -> OHLCVResponse:
"""
Download historic ohlcv
@@ -2302,7 +2308,7 @@ class Exchange:
pair: str,
timeframe: str,
candle_type: CandleType,
- since_ms: Optional[int],
+ since_ms: int | None,
cache: bool,
) -> Coroutine[Any, Any, OHLCVResponse]:
not_all_data = cache and self.required_candle_call_count > 1
@@ -2371,7 +2377,7 @@ class Exchange:
)
def _build_ohlcv_dl_jobs(
- self, pair_list: ListPairsWithTimeframes, since_ms: Optional[int], cache: bool
+ self, pair_list: ListPairsWithTimeframes, since_ms: int | None, cache: bool
) -> tuple[list[Coroutine], list[PairWithTimeframe]]:
"""
Build Coroutines to execute as part of refresh_latest_ohlcv
@@ -2448,9 +2454,9 @@ class Exchange:
self,
pair_list: ListPairsWithTimeframes,
*,
- since_ms: Optional[int] = None,
+ since_ms: int | None = None,
cache: bool = True,
- drop_incomplete: Optional[bool] = None,
+ drop_incomplete: bool | None = None,
) -> dict[PairWithTimeframe, DataFrame]:
"""
Refresh in-memory OHLCV asynchronously and set `_klines` with the result
@@ -2544,7 +2550,7 @@ class Exchange:
pair: str,
timeframe: str,
candle_type: CandleType,
- since_ms: Optional[int] = None,
+ since_ms: int | None = None,
) -> OHLCVResponse:
"""
Asynchronously get candle history data using fetch_ohlcv
@@ -2618,7 +2624,7 @@ class Exchange:
pair: str,
timeframe: str,
limit: int,
- since_ms: Optional[int] = None,
+ since_ms: int | None = None,
) -> list[list]:
"""
Fetch funding rate history - used to selectively override this by subclasses.
@@ -2677,7 +2683,7 @@ class Exchange:
async def _build_trades_dl_jobs(
self, pairwt: PairWithTimeframe, data_handler, cache: bool
- ) -> tuple[PairWithTimeframe, Optional[DataFrame]]:
+ ) -> tuple[PairWithTimeframe, DataFrame | None]:
"""
Build coroutines to refresh trades for (they're then called through async.gather)
"""
@@ -2821,7 +2827,7 @@ class Exchange:
@retrier_async
async def _async_fetch_trades(
- self, pair: str, since: Optional[int] = None, params: Optional[dict] = None
+ self, pair: str, since: int | None = None, params: dict | None = None
) -> tuple[list[list], Any]:
"""
Asynchronously gets trade history using fetch_trades.
@@ -2881,7 +2887,7 @@ class Exchange:
return trades[-1].get("timestamp")
async def _async_get_trade_history_id(
- self, pair: str, until: int, since: Optional[int] = None, from_id: Optional[str] = None
+ self, pair: str, until: int, since: int | None = None, from_id: str | None = None
) -> tuple[str, list[list]]:
"""
Asynchronously gets trade history using fetch_trades
@@ -2936,7 +2942,7 @@ class Exchange:
return (pair, trades)
async def _async_get_trade_history_time(
- self, pair: str, until: int, since: Optional[int] = None
+ self, pair: str, until: int, since: int | None = None
) -> tuple[str, list[list]]:
"""
Asynchronously gets trade history using fetch_trades,
@@ -2977,9 +2983,9 @@ class Exchange:
async def _async_get_trade_history(
self,
pair: str,
- since: Optional[int] = None,
- until: Optional[int] = None,
- from_id: Optional[str] = None,
+ since: int | None = None,
+ until: int | None = None,
+ from_id: str | None = None,
) -> tuple[str, list[list]]:
"""
Async wrapper handling downloading trades using either time or id based methods.
@@ -3008,9 +3014,9 @@ class Exchange:
def get_historic_trades(
self,
pair: str,
- since: Optional[int] = None,
- until: Optional[int] = None,
- from_id: Optional[str] = None,
+ since: int | None = None,
+ until: int | None = None,
+ from_id: str | None = None,
) -> tuple[str, list]:
"""
Get trade history data using asyncio.
@@ -3039,7 +3045,7 @@ class Exchange:
return self.loop.run_until_complete(task)
@retrier
- def _get_funding_fees_from_exchange(self, pair: str, since: Union[datetime, int]) -> float:
+ def _get_funding_fees_from_exchange(self, pair: str, since: datetime | int) -> float:
"""
Returns the sum of all funding fees that were exchanged for a pair within a timeframe
Dry-run handling happens as part of _calculate_funding_fees.
@@ -3170,8 +3176,8 @@ class Exchange:
file_dump_json(filename, data)
def load_cached_leverage_tiers(
- self, stake_currency: str, cache_time: Optional[timedelta] = None
- ) -> Optional[dict[str, list[dict]]]:
+ self, stake_currency: str, cache_time: timedelta | None = None
+ ) -> dict[str, list[dict]] | None:
"""
Load cached leverage tiers from disk
:param cache_time: The maximum age of the cache before it is considered outdated
@@ -3216,7 +3222,7 @@ class Exchange:
"maintAmt": float(info["cum"]) if "cum" in info else None,
}
- def get_max_leverage(self, pair: str, stake_amount: Optional[float]) -> float:
+ def get_max_leverage(self, pair: str, stake_amount: float | None) -> float:
"""
Returns the maximum leverage that a pair can be traded at
:param pair: The base/quote currency pair being traded
@@ -3294,7 +3300,7 @@ class Exchange:
def _set_leverage(
self,
leverage: float,
- pair: Optional[str] = None,
+ pair: str | None = None,
accept_fail: bool = False,
):
"""
@@ -3346,7 +3352,7 @@ class Exchange:
pair: str,
margin_mode: MarginMode,
accept_fail: bool = False,
- params: Optional[dict] = None,
+ params: dict | None = None,
):
"""
Set's the margin mode on the exchange to cross or isolated for a specific pair
@@ -3381,7 +3387,7 @@ class Exchange:
amount: float,
is_short: bool,
open_date: datetime,
- close_date: Optional[datetime] = None,
+ close_date: datetime | None = None,
) -> float:
"""
Fetches and calculates the sum of all funding fees that occurred for a pair
@@ -3434,7 +3440,7 @@ class Exchange:
@staticmethod
def combine_funding_and_mark(
- funding_rates: DataFrame, mark_rates: DataFrame, futures_funding_rate: Optional[int] = None
+ funding_rates: DataFrame, mark_rates: DataFrame, futures_funding_rate: int | None = None
) -> DataFrame:
"""
Combine funding-rates and mark-rates dataframes
@@ -3475,7 +3481,7 @@ class Exchange:
is_short: bool,
open_date: datetime,
close_date: datetime,
- time_in_ratio: Optional[float] = None,
+ time_in_ratio: float | None = None,
) -> float:
"""
calculates the sum of all funding fees that occurred for a pair during a futures trade
@@ -3533,8 +3539,8 @@ class Exchange:
stake_amount: float,
leverage: float,
wallet_balance: float,
- open_trades: Optional[list] = None,
- ) -> Optional[float]:
+ open_trades: list | None = None,
+ ) -> float | None:
"""
Set's the margin mode on the exchange to cross or isolated for a specific pair
"""
@@ -3582,7 +3588,7 @@ class Exchange:
leverage: float,
wallet_balance: float, # Or margin balance
open_trades: list,
- ) -> Optional[float]:
+ ) -> float | None:
"""
Important: Must be fetching data from cached values as this is used by backtesting!
PERPETUAL:
@@ -3633,7 +3639,7 @@ class Exchange:
self,
pair: str,
notional_value: float,
- ) -> tuple[float, Optional[float]]:
+ ) -> tuple[float, float | None]:
"""
Important: Must be fetching data from cached values as this is used by backtesting!
:param pair: Market symbol
diff --git a/freqtrade/exchange/exchange_types.py b/freqtrade/exchange/exchange_types.py
index e9c58ec38..2e490e1bd 100644
--- a/freqtrade/exchange/exchange_types.py
+++ b/freqtrade/exchange/exchange_types.py
@@ -1,4 +1,4 @@
-from typing import Optional, TypedDict
+from typing import Any, Literal, TypedDict
from freqtrade.enums import CandleType
@@ -11,7 +11,7 @@ class FtHas(TypedDict, total=False):
# Stoploss on exchange
stoploss_on_exchange: bool
stop_price_param: str
- stop_price_prop: str
+ stop_price_prop: Literal["stopPrice", "stopLossPrice"]
stop_price_type_field: str
stop_price_type_value_mapping: dict
stoploss_order_types: dict[str, str]
@@ -35,7 +35,7 @@ class FtHas(TypedDict, total=False):
trades_has_history: bool
trades_pagination_overlap: bool
# Orderbook
- l2_limit_range: Optional[list[int]]
+ l2_limit_range: list[int] | None
l2_limit_range_required: bool
# Futures
ccxt_futures_name: str # usually swap
@@ -44,7 +44,7 @@ class FtHas(TypedDict, total=False):
funding_fee_timeframe: str
floor_leverage: bool
needs_trading_fees: bool
- order_props_in_contracts: list[str]
+ order_props_in_contracts: list[Literal["amount", "cost", "filled", "remaining"]]
# Websocket control
ws_enabled: bool
@@ -52,14 +52,14 @@ class FtHas(TypedDict, total=False):
class Ticker(TypedDict):
symbol: str
- ask: Optional[float]
- askVolume: Optional[float]
- bid: Optional[float]
- bidVolume: Optional[float]
- last: Optional[float]
- quoteVolume: Optional[float]
- baseVolume: Optional[float]
- percentage: Optional[float]
+ ask: float | None
+ askVolume: float | None
+ bid: float | None
+ bidVolume: float | None
+ last: float | None
+ quoteVolume: float | None
+ baseVolume: float | None
+ percentage: float | None
# Several more - only listing required.
@@ -70,9 +70,9 @@ class OrderBook(TypedDict):
symbol: str
bids: list[tuple[float, float]]
asks: list[tuple[float, float]]
- timestamp: Optional[int]
- datetime: Optional[str]
- nonce: Optional[int]
+ timestamp: int | None
+ datetime: str | None
+ nonce: int | None
class CcxtBalance(TypedDict):
@@ -89,10 +89,12 @@ class CcxtPosition(TypedDict):
side: str
contracts: float
leverage: float
- collateral: Optional[float]
- initialMargin: Optional[float]
- liquidationPrice: Optional[float]
+ collateral: float | None
+ initialMargin: float | None
+ liquidationPrice: float | None
+CcxtOrder = dict[str, Any]
+
# pair, timeframe, candleType, OHLCV, drop last?,
OHLCVResponse = tuple[str, str, CandleType, list, bool]
diff --git a/freqtrade/exchange/exchange_utils.py b/freqtrade/exchange/exchange_utils.py
index f7b53a836..1c8a30001 100644
--- a/freqtrade/exchange/exchange_utils.py
+++ b/freqtrade/exchange/exchange_utils.py
@@ -5,7 +5,7 @@ Exchange support utils
import inspect
from datetime import datetime, timedelta, timezone
from math import ceil, floor
-from typing import Any, Optional
+from typing import Any
import ccxt
from ccxt import (
@@ -33,20 +33,18 @@ from freqtrade.util import FtPrecise
CcxtModuleType = Any
-def is_exchange_known_ccxt(
- exchange_name: str, ccxt_module: Optional[CcxtModuleType] = None
-) -> bool:
+def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType | None = None) -> bool:
return exchange_name in ccxt_exchanges(ccxt_module)
-def ccxt_exchanges(ccxt_module: Optional[CcxtModuleType] = None) -> list[str]:
+def ccxt_exchanges(ccxt_module: CcxtModuleType | None = None) -> list[str]:
"""
Return the list of all exchanges known to ccxt
"""
return ccxt_module.exchanges if ccxt_module is not None else ccxt.exchanges
-def available_exchanges(ccxt_module: Optional[CcxtModuleType] = None) -> list[str]:
+def available_exchanges(ccxt_module: CcxtModuleType | None = None) -> list[str]:
"""
Return exchanges available to the bot, i.e. non-bad exchanges in the ccxt list
"""
@@ -54,7 +52,7 @@ def available_exchanges(ccxt_module: Optional[CcxtModuleType] = None) -> list[st
return [x for x in exchanges if validate_exchange(x)[0]]
-def validate_exchange(exchange: str) -> tuple[bool, str, Optional[ccxt.Exchange]]:
+def validate_exchange(exchange: str) -> tuple[bool, str, ccxt.Exchange | None]:
"""
returns: can_use, reason, exchange_object
with Reason including both missing and missing_opt
@@ -137,9 +135,7 @@ def list_available_exchanges(all_exchanges: bool) -> list[ValidExchangesType]:
return exchanges_valid
-def date_minus_candles(
- timeframe: str, candle_count: int, date: Optional[datetime] = None
-) -> datetime:
+def date_minus_candles(timeframe: str, candle_count: int, date: datetime | None = None) -> datetime:
"""
subtract X candles from a date.
:param timeframe: timeframe in string format (e.g. "5m")
@@ -166,7 +162,7 @@ def market_is_active(market: dict) -> bool:
return market.get("active", True) is not False
-def amount_to_contracts(amount: float, contract_size: Optional[float]) -> float:
+def amount_to_contracts(amount: float, contract_size: float | None) -> float:
"""
Convert amount to contracts.
:param amount: amount to convert
@@ -179,7 +175,7 @@ def amount_to_contracts(amount: float, contract_size: Optional[float]) -> float:
return amount
-def contracts_to_amount(num_contracts: float, contract_size: Optional[float]) -> float:
+def contracts_to_amount(num_contracts: float, contract_size: float | None) -> float:
"""
Takes num-contracts and converts it to contract size
:param num_contracts: number of contracts
@@ -194,7 +190,7 @@ def contracts_to_amount(num_contracts: float, contract_size: Optional[float]) ->
def amount_to_precision(
- amount: float, amount_precision: Optional[float], precisionMode: Optional[int]
+ amount: float, amount_precision: float | None, precisionMode: int | None
) -> float:
"""
Returns the amount to buy or sell to a precision the Exchange accepts
@@ -224,9 +220,9 @@ def amount_to_precision(
def amount_to_contract_precision(
amount,
- amount_precision: Optional[float],
- precisionMode: Optional[int],
- contract_size: Optional[float],
+ amount_precision: float | None,
+ precisionMode: int | None,
+ contract_size: float | None,
) -> float:
"""
Returns the amount to buy or sell to a precision the Exchange accepts
@@ -285,8 +281,8 @@ def __price_to_precision_significant_digits(
def price_to_precision(
price: float,
- price_precision: Optional[float],
- precisionMode: Optional[int],
+ price_precision: float | None,
+ precisionMode: int | None,
*,
rounding_mode: int = ROUND,
) -> float:
diff --git a/freqtrade/exchange/exchange_utils_timeframe.py b/freqtrade/exchange/exchange_utils_timeframe.py
index 67cf1b5d6..494df58f2 100644
--- a/freqtrade/exchange/exchange_utils_timeframe.py
+++ b/freqtrade/exchange/exchange_utils_timeframe.py
@@ -1,5 +1,4 @@
from datetime import datetime, timezone
-from typing import Optional
import ccxt
from ccxt import ROUND_DOWN, ROUND_UP
@@ -51,7 +50,7 @@ def timeframe_to_resample_freq(timeframe: str) -> str:
return resample_interval
-def timeframe_to_prev_date(timeframe: str, date: Optional[datetime] = None) -> datetime:
+def timeframe_to_prev_date(timeframe: str, date: datetime | None = None) -> datetime:
"""
Use Timeframe and determine the candle start date for this date.
Does not round when given a candle start date.
@@ -66,7 +65,7 @@ def timeframe_to_prev_date(timeframe: str, date: Optional[datetime] = None) -> d
return dt_from_ts(new_timestamp)
-def timeframe_to_next_date(timeframe: str, date: Optional[datetime] = None) -> datetime:
+def timeframe_to_next_date(timeframe: str, date: datetime | None = None) -> datetime:
"""
Use Timeframe and determine next candle.
:param timeframe: timeframe in string format (e.g. "5m")
diff --git a/freqtrade/exchange/gate.py b/freqtrade/exchange/gate.py
index 70f877210..123676fba 100644
--- a/freqtrade/exchange/gate.py
+++ b/freqtrade/exchange/gate.py
@@ -2,12 +2,11 @@
import logging
from datetime import datetime
-from typing import Any, Optional
from freqtrade.constants import BuySell
from freqtrade.enums import MarginMode, PriceType, TradingMode
from freqtrade.exchange import Exchange
-from freqtrade.exchange.exchange_types import FtHas
+from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
from freqtrade.misc import safe_value_fallback2
@@ -74,7 +73,7 @@ class Gate(Exchange):
return params
def get_trades_for_order(
- self, order_id: str, pair: str, since: datetime, params: Optional[dict] = None
+ self, order_id: str, pair: str, since: datetime, params: dict | None = None
) -> list:
trades = super().get_trades_for_order(order_id, pair, since, params)
@@ -99,10 +98,12 @@ class Gate(Exchange):
}
return trades
- def get_order_id_conditional(self, order: dict[str, Any]) -> str:
+ def get_order_id_conditional(self, order: CcxtOrder) -> str:
return safe_value_fallback2(order, order, "id_stop", "id")
- def fetch_stoploss_order(self, order_id: str, pair: str, params: Optional[dict] = None) -> dict:
+ def fetch_stoploss_order(
+ self, order_id: str, pair: str, params: dict | None = None
+ ) -> CcxtOrder:
order = self.fetch_order(order_id=order_id, pair=pair, params={"stop": True})
if order.get("status", "open") == "closed":
# Places a real order - which we need to fetch explicitly.
@@ -119,7 +120,5 @@ class Gate(Exchange):
return order1
return order
- def cancel_stoploss_order(
- self, order_id: str, pair: str, params: Optional[dict] = None
- ) -> dict:
+ def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict:
return self.cancel_order(order_id=order_id, pair=pair, params={"stop": True})
diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py
index 7dea0e435..365b88681 100644
--- a/freqtrade/exchange/kraken.py
+++ b/freqtrade/exchange/kraken.py
@@ -2,7 +2,7 @@
import logging
from datetime import datetime
-from typing import Any, Optional
+from typing import Any
import ccxt
from pandas import DataFrame
@@ -50,7 +50,7 @@ class Kraken(Exchange):
return parent_check and market.get("darkpool", False) is False
- def get_tickers(self, symbols: Optional[list[str]] = None, cached: bool = False) -> Tickers:
+ def get_tickers(self, symbols: list[str] | None = None, cached: bool = False) -> Tickers:
# Only fetch tickers for current stake currency
# Otherwise the request for kraken becomes too large.
symbols = list(self.get_markets(quote_currencies=[self._config["stake_currency"]]))
@@ -99,7 +99,7 @@ class Kraken(Exchange):
def _set_leverage(
self,
leverage: float,
- pair: Optional[str] = None,
+ pair: str | None = None,
accept_fail: bool = False,
):
"""
@@ -137,7 +137,7 @@ class Kraken(Exchange):
is_short: bool,
open_date: datetime,
close_date: datetime,
- time_in_ratio: Optional[float] = None,
+ time_in_ratio: float | None = None,
) -> float:
"""
# ! This method will always error when run by Freqtrade because time_in_ratio is never
diff --git a/freqtrade/exchange/kucoin.py b/freqtrade/exchange/kucoin.py
index fc4433f0b..de033dcc0 100644
--- a/freqtrade/exchange/kucoin.py
+++ b/freqtrade/exchange/kucoin.py
@@ -4,7 +4,7 @@ import logging
from freqtrade.constants import BuySell
from freqtrade.exchange import Exchange
-from freqtrade.exchange.exchange_types import FtHas
+from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
logger = logging.getLogger(__name__)
@@ -47,7 +47,7 @@ class Kucoin(Exchange):
leverage: float,
reduceOnly: bool = False,
time_in_force: str = "GTC",
- ) -> dict:
+ ) -> CcxtOrder:
res = super().create_order(
pair=pair,
ordertype=ordertype,
diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py
index fbbf21757..e9784f12e 100644
--- a/freqtrade/exchange/okx.py
+++ b/freqtrade/exchange/okx.py
@@ -1,6 +1,5 @@
import logging
from datetime import timedelta
-from typing import Any, Optional
import ccxt
@@ -14,7 +13,7 @@ from freqtrade.exceptions import (
)
from freqtrade.exchange import Exchange, date_minus_candles
from freqtrade.exchange.common import API_RETRY_COUNT, retrier
-from freqtrade.exchange.exchange_types import FtHas
+from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
from freqtrade.misc import safe_value_fallback2
from freqtrade.util import dt_now, dt_ts
@@ -60,7 +59,7 @@ class Okx(Exchange):
_ccxt_params: dict = {"options": {"brokerId": "ffb5405ad327SUDE"}}
def ohlcv_candle_limit(
- self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None
+ self, timeframe: str, candle_type: CandleType, since_ms: int | None = None
) -> int:
"""
Exchange ohlcv candle limit
@@ -191,7 +190,7 @@ class Okx(Exchange):
params["posSide"] = self._get_posSide(side, True)
return params
- def _convert_stop_order(self, pair: str, order_id: str, order: dict) -> dict:
+ def _convert_stop_order(self, pair: str, order_id: str, order: CcxtOrder) -> CcxtOrder:
if (
order.get("status", "open") == "closed"
and (real_order_id := order.get("info", {}).get("ordId")) is not None
@@ -209,7 +208,9 @@ class Okx(Exchange):
return order
@retrier(retries=API_RETRY_COUNT)
- def fetch_stoploss_order(self, order_id: str, pair: str, params: Optional[dict] = None) -> dict:
+ def fetch_stoploss_order(
+ self, order_id: str, pair: str, params: dict | None = None
+ ) -> CcxtOrder:
if self._config["dry_run"]:
return self.fetch_dry_run_order(order_id)
@@ -231,7 +232,7 @@ class Okx(Exchange):
return self._fetch_stop_order_fallback(order_id, pair)
- def _fetch_stop_order_fallback(self, order_id: str, pair: str) -> dict:
+ def _fetch_stop_order_fallback(self, order_id: str, pair: str) -> CcxtOrder:
params2 = {"stop": True, "ordType": "conditional"}
for method in (
self._api.fetch_open_orders,
@@ -256,14 +257,12 @@ class Okx(Exchange):
raise OperationalException(e) from e
raise RetryableOrderError(f"StoplossOrder not found (pair: {pair} id: {order_id}).")
- def get_order_id_conditional(self, order: dict[str, Any]) -> str:
+ def get_order_id_conditional(self, order: CcxtOrder) -> str:
if order.get("type", "") == "stop":
return safe_value_fallback2(order, order, "id_stop", "id")
return order["id"]
- def cancel_stoploss_order(
- self, order_id: str, pair: str, params: Optional[dict] = None
- ) -> dict:
+ def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict:
params1 = {"stop": True}
# 'ordType': 'conditional'
#
@@ -273,7 +272,7 @@ class Okx(Exchange):
params=params1,
)
- def _fetch_orders_emulate(self, pair: str, since_ms: int) -> list[dict]:
+ def _fetch_orders_emulate(self, pair: str, since_ms: int) -> list[CcxtOrder]:
orders = []
orders = self._api.fetch_closed_orders(pair, since=since_ms)
diff --git a/freqtrade/freqai/RL/BaseEnvironment.py b/freqtrade/freqai/RL/BaseEnvironment.py
index 021a2fc58..d9142046c 100644
--- a/freqtrade/freqai/RL/BaseEnvironment.py
+++ b/freqtrade/freqai/RL/BaseEnvironment.py
@@ -2,7 +2,6 @@ import logging
import random
from abc import abstractmethod
from enum import Enum
-from typing import Optional, Union
import gymnasium as gym
import numpy as np
@@ -140,7 +139,7 @@ class BaseEnvironment(gym.Env):
self._end_tick: int = len(self.prices) - 1
self._done: bool = False
self._current_tick: int = self._start_tick
- self._last_trade_tick: Optional[int] = None
+ self._last_trade_tick: int | None = None
self._position = Positions.Neutral
self._position_history: list = [None]
self.total_reward: float = 0
@@ -173,8 +172,8 @@ class BaseEnvironment(gym.Env):
def tensorboard_log(
self,
metric: str,
- value: Optional[Union[int, float]] = None,
- inc: Optional[bool] = None,
+ value: int | float | None = None,
+ inc: bool | None = None,
category: str = "custom",
):
"""
diff --git a/freqtrade/freqai/RL/BaseReinforcementLearningModel.py b/freqtrade/freqai/RL/BaseReinforcementLearningModel.py
index 5da88adb6..3c3c84804 100644
--- a/freqtrade/freqai/RL/BaseReinforcementLearningModel.py
+++ b/freqtrade/freqai/RL/BaseReinforcementLearningModel.py
@@ -2,9 +2,10 @@ import copy
import importlib
import logging
from abc import abstractmethod
+from collections.abc import Callable
from datetime import datetime, timezone
from pathlib import Path
-from typing import Any, Callable, Optional, Union
+from typing import Any
import gymnasium as gym
import numpy as np
@@ -49,9 +50,9 @@ class BaseReinforcementLearningModel(IFreqaiModel):
)
th.set_num_threads(self.max_threads)
self.reward_params = self.freqai_info["rl_config"]["model_reward_parameters"]
- self.train_env: Union[VecMonitor, SubprocVecEnv, gym.Env] = gym.Env()
- self.eval_env: Union[VecMonitor, SubprocVecEnv, gym.Env] = gym.Env()
- self.eval_callback: Optional[MaskableEvalCallback] = None
+ self.train_env: VecMonitor | SubprocVecEnv | gym.Env = gym.Env()
+ self.eval_env: VecMonitor | SubprocVecEnv | gym.Env = gym.Env()
+ self.eval_callback: MaskableEvalCallback | None = None
self.model_type = self.freqai_info["rl_config"]["model_type"]
self.rl_config = self.freqai_info["rl_config"]
self.df_raw: DataFrame = DataFrame()
diff --git a/freqtrade/freqai/data_kitchen.py b/freqtrade/freqai/data_kitchen.py
index 4663c41b0..5292dc0af 100644
--- a/freqtrade/freqai/data_kitchen.py
+++ b/freqtrade/freqai/data_kitchen.py
@@ -5,7 +5,7 @@ import random
import shutil
from datetime import datetime, timezone
from pathlib import Path
-from typing import Any, Optional
+from typing import Any
import numpy as np
import numpy.typing as npt
@@ -111,7 +111,7 @@ class FreqaiDataKitchen:
def set_paths(
self,
pair: str,
- trained_timestamp: Optional[int] = None,
+ trained_timestamp: int | None = None,
) -> None:
"""
Set the paths to the data for the present coin/botloop
diff --git a/freqtrade/freqai/freqai_interface.py b/freqtrade/freqai/freqai_interface.py
index 950f40ca4..7d7d605d7 100644
--- a/freqtrade/freqai/freqai_interface.py
+++ b/freqtrade/freqai/freqai_interface.py
@@ -5,7 +5,7 @@ from abc import ABC, abstractmethod
from collections import deque
from datetime import datetime, timezone
from pathlib import Path
-from typing import Any, Literal, Optional
+from typing import Any, Literal
import datasieve.transforms as ds
import numpy as np
@@ -106,7 +106,7 @@ class IFreqaiModel(ABC):
self._threads: list[threading.Thread] = []
self._stop_event = threading.Event()
self.metadata: dict[str, Any] = self.dd.load_global_metadata_from_disk()
- self.data_provider: Optional[DataProvider] = None
+ self.data_provider: DataProvider | None = None
self.max_system_threads = max(int(psutil.cpu_count() * 2 - 2), 1)
self.can_short = True # overridden in start() with strategy.can_short
self.model: Any = None
@@ -185,6 +185,7 @@ class IFreqaiModel(ABC):
Callback for Subclasses to override to include logic for shutting down resources
when SIGINT is sent.
"""
+ self.dd.save_historic_predictions_to_disk()
return
def shutdown(self):
@@ -198,9 +199,16 @@ class IFreqaiModel(ABC):
self.data_provider = None
self._on_stop()
- logger.info("Waiting on Training iteration")
- for _thread in self._threads:
- _thread.join()
+ if self.freqai_info.get("wait_for_training_iteration_on_reload", True):
+ logger.info("Waiting on Training iteration")
+ for _thread in self._threads:
+ _thread.join()
+ else:
+ logger.warning(
+ "Breaking current training iteration because "
+ "you set wait_for_training_iteration_on_reload to "
+ " False."
+ )
def start_scanning(self, *args, **kwargs) -> None:
"""
@@ -286,7 +294,9 @@ class IFreqaiModel(ABC):
# tr_backtest is the backtesting time range e.g. the week directly
# following tr_train. Both of these windows slide through the
# entire backtest
- for tr_train, tr_backtest in zip(dk.training_timeranges, dk.backtesting_timeranges):
+ for tr_train, tr_backtest in zip(
+ dk.training_timeranges, dk.backtesting_timeranges, strict=False
+ ):
(_, _) = self.dd.get_pair_dict_info(pair)
train_it += 1
total_trains = len(dk.backtesting_timeranges)
diff --git a/freqtrade/freqai/prediction_models/ReinforcementLearner.py b/freqtrade/freqai/prediction_models/ReinforcementLearner.py
index 5bd119fbe..87c3450dc 100644
--- a/freqtrade/freqai/prediction_models/ReinforcementLearner.py
+++ b/freqtrade/freqai/prediction_models/ReinforcementLearner.py
@@ -1,6 +1,6 @@
import logging
from pathlib import Path
-from typing import Any, Optional
+from typing import Any
import torch as th
from stable_baselines3.common.callbacks import ProgressBarCallback
@@ -78,7 +78,7 @@ class ReinforcementLearner(BaseReinforcementLearningModel):
model = self.dd.model_dictionary[dk.pair]
model.set_env(self.train_env)
callbacks: list[Any] = [self.eval_callback, self.tensorboard_callback]
- progressbar_callback: Optional[ProgressBarCallback] = None
+ progressbar_callback: ProgressBarCallback | None = None
if self.rl_config.get("progress_bar", False):
progressbar_callback = ProgressBarCallback()
callbacks.insert(0, progressbar_callback)
diff --git a/freqtrade/freqai/tensorboard/TensorboardCallback.py b/freqtrade/freqai/tensorboard/TensorboardCallback.py
index 2b2b532f3..0ffbb5838 100644
--- a/freqtrade/freqai/tensorboard/TensorboardCallback.py
+++ b/freqtrade/freqai/tensorboard/TensorboardCallback.py
@@ -1,5 +1,5 @@
from enum import Enum
-from typing import Any, Union
+from typing import Any
from stable_baselines3.common.callbacks import BaseCallback
from stable_baselines3.common.logger import HParam
@@ -27,7 +27,7 @@ class TensorboardCallback(BaseCallback):
# "batch_size": self.model.batch_size,
# "n_steps": self.model.n_steps,
}
- metric_dict: dict[str, Union[float, int]] = {
+ metric_dict: dict[str, float | int] = {
"eval/mean_reward": 0,
"rollout/ep_rew_mean": 0,
"rollout/ep_len_mean": 0,
diff --git a/freqtrade/freqai/tensorboard/tensorboard.py b/freqtrade/freqai/tensorboard/tensorboard.py
index 81f48047e..76bd5c2f8 100644
--- a/freqtrade/freqai/tensorboard/tensorboard.py
+++ b/freqtrade/freqai/tensorboard/tensorboard.py
@@ -45,7 +45,7 @@ class TensorBoardCallback(BaseTensorBoardCallback):
return False
evals = ["validation", "train"]
- for metric, eval_ in zip(evals_log.items(), evals):
+ for metric, eval_ in zip(evals_log.items(), evals, strict=False):
for metric_name, log in metric[1].items():
score = log[-1][0] if isinstance(log[-1], tuple) else log[-1]
self.writer.add_scalar(f"{eval_}-{metric_name}", score, epoch)
diff --git a/freqtrade/freqai/torch/PyTorchModelTrainer.py b/freqtrade/freqai/torch/PyTorchModelTrainer.py
index 02aa712d5..8682cff69 100644
--- a/freqtrade/freqai/torch/PyTorchModelTrainer.py
+++ b/freqtrade/freqai/torch/PyTorchModelTrainer.py
@@ -1,6 +1,6 @@
import logging
from pathlib import Path
-from typing import Any, Optional
+from typing import Any
import pandas as pd
import torch
@@ -50,8 +50,8 @@ class PyTorchModelTrainer(PyTorchTrainerInterface):
self.criterion = criterion
self.model_meta_data = model_meta_data
self.device = device
- self.n_epochs: Optional[int] = kwargs.get("n_epochs", 10)
- self.n_steps: Optional[int] = kwargs.get("n_steps", None)
+ self.n_epochs: int | None = kwargs.get("n_epochs", 10)
+ self.n_steps: int | None = kwargs.get("n_steps", None)
if self.n_steps is None and not self.n_epochs:
raise Exception("Either `n_steps` or `n_epochs` should be set.")
diff --git a/freqtrade/freqai/utils.py b/freqtrade/freqai/utils.py
index 9e14fa930..d2a15046d 100644
--- a/freqtrade/freqai/utils.py
+++ b/freqtrade/freqai/utils.py
@@ -107,7 +107,7 @@ def plot_feature_importance(
# Extract feature importance from model
models = {}
if "FreqaiMultiOutputRegressor" in str(model.__class__):
- for estimator, label in zip(model.estimators_, dk.label_list):
+ for estimator, label in zip(model.estimators_, dk.label_list, strict=False):
models[label] = estimator
else:
models[dk.label_list[0]] = model
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 0ca107b17..27be9dae1 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -9,7 +9,7 @@ from datetime import datetime, time, timedelta, timezone
from math import isclose
from threading import Lock
from time import sleep
-from typing import Any, Optional
+from typing import Any
from schedule import Scheduler
@@ -43,6 +43,7 @@ from freqtrade.exchange import (
timeframe_to_next_date,
timeframe_to_seconds,
)
+from freqtrade.exchange.exchange_types import CcxtOrder
from freqtrade.leverage.liquidation_price import update_liquidation_prices
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
from freqtrade.mixins import LoggingMixin
@@ -111,7 +112,7 @@ class FreqtradeBot(LoggingMixin):
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: Optional[datetime] = 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,
@@ -325,7 +326,7 @@ class FreqtradeBot(LoggingMixin):
}
self.rpc.send_msg(msg)
- def _refresh_active_whitelist(self, trades: Optional[list[Trade]] = None) -> list[str]:
+ def _refresh_active_whitelist(self, trades: list[Trade] | None = None) -> list[str]:
"""
Refresh active whitelist from pairlist or edge and extend it with
pairs that have open trades.
@@ -579,7 +580,7 @@ class FreqtradeBot(LoggingMixin):
logger.warning(
f"{trade} has a total of {trade.amount} {trade.base_currency}, "
f"but the Wallet shows a total of {total} {trade.base_currency}. "
- f"Adjusting trade amount to {total}."
+ f"Adjusting trade amount to {total}. "
"This may however lead to further issues."
)
trade.amount = total
@@ -587,7 +588,7 @@ class FreqtradeBot(LoggingMixin):
logger.warning(
f"{trade} has a total of {trade.amount} {trade.base_currency}, "
f"but the Wallet shows a total of {total} {trade.base_currency}. "
- "Refusing to adjust as the difference is too large."
+ "Refusing to adjust as the difference is too large. "
"This may however lead to further issues."
)
if prev_trade_amount != trade.amount:
@@ -862,14 +863,14 @@ class FreqtradeBot(LoggingMixin):
self,
pair: str,
stake_amount: float,
- price: Optional[float] = None,
+ price: float | None = None,
*,
is_short: bool = False,
- ordertype: Optional[str] = None,
- enter_tag: Optional[str] = None,
- trade: Optional[Trade] = None,
+ ordertype: str | None = None,
+ enter_tag: str | None = None,
+ trade: Trade | None = None,
mode: EntryExecuteMode = "initial",
- leverage_: Optional[float] = None,
+ leverage_: float | None = None,
) -> bool:
"""
Executes an entry for the given pair
@@ -1078,13 +1079,13 @@ class FreqtradeBot(LoggingMixin):
def get_valid_enter_price_and_stake(
self,
pair: str,
- price: Optional[float],
+ price: float | None,
stake_amount: float,
trade_side: LongShort,
- entry_tag: Optional[str],
- trade: Optional[Trade],
+ entry_tag: str | None,
+ trade: Trade | None,
mode: EntryExecuteMode,
- leverage_: Optional[float],
+ leverage_: float | None,
) -> tuple[float, float, float]:
"""
Validate and eventually adjust (within limits) limit, amount and leverage
@@ -1180,7 +1181,7 @@ class FreqtradeBot(LoggingMixin):
self,
trade: Trade,
order: Order,
- order_type: Optional[str],
+ order_type: str | None,
fill: bool = False,
sub_trade: bool = False,
) -> None:
@@ -1195,6 +1196,13 @@ class FreqtradeBot(LoggingMixin):
current_rate = self.exchange.get_rate(
trade.pair, side="entry", is_short=trade.is_short, refresh=False
)
+ stake_amount = trade.stake_amount
+ if not fill:
+ # If we have open orders, we need to add the stake amount of the open orders
+ # as it's not yet included in the trade.stake_amount
+ stake_amount += sum(
+ o.stake_amount for o in trade.open_orders if o.ft_order_side == trade.entry_side
+ )
msg: RPCEntryMsg = {
"trade_id": trade.id,
@@ -1208,12 +1216,12 @@ class FreqtradeBot(LoggingMixin):
"limit": open_rate, # Deprecated (?)
"open_rate": open_rate,
"order_type": order_type or "unknown",
- "stake_amount": trade.stake_amount,
+ "stake_amount": stake_amount,
"stake_currency": self.config["stake_currency"],
"base_currency": self.exchange.get_pair_base_currency(trade.pair),
"quote_currency": self.exchange.get_pair_quote_currency(trade.pair),
"fiat_currency": self.config.get("fiat_display_currency", None),
- "amount": order.safe_amount_after_fee if fill else (order.amount or trade.amount),
+ "amount": order.safe_amount_after_fee if fill else (order.safe_amount or trade.amount),
"open_date": trade.open_date_utc or datetime.now(timezone.utc),
"current_rate": current_rate,
"sub_trade": sub_trade,
@@ -1344,7 +1352,7 @@ class FreqtradeBot(LoggingMixin):
return False
def _check_and_execute_exit(
- self, trade: Trade, exit_rate: float, enter: bool, exit_: bool, exit_tag: Optional[str]
+ self, trade: Trade, exit_rate: float, enter: bool, exit_: bool, exit_tag: str | None
) -> bool:
"""
Check and execute trade exit
@@ -1466,7 +1474,7 @@ class FreqtradeBot(LoggingMixin):
return False
- def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: dict) -> None:
+ def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: CcxtOrder) -> None:
"""
Check to see if stoploss on exchange should be updated
in case of trailing stoploss on exchange
@@ -1504,7 +1512,7 @@ class FreqtradeBot(LoggingMixin):
f"Could not create trailing stoploss order for pair {trade.pair}."
)
- def manage_trade_stoploss_orders(self, trade: Trade, stoploss_orders: list[dict]):
+ def manage_trade_stoploss_orders(self, trade: Trade, stoploss_orders: list[CcxtOrder]):
"""
Perform required actions according to existing stoploss orders of trade
:param trade: Corresponding Trade
@@ -1580,7 +1588,9 @@ class FreqtradeBot(LoggingMixin):
else:
self.replace_order(order, open_order, trade)
- def handle_cancel_order(self, order: dict, order_obj: Order, trade: Trade, reason: str) -> None:
+ def handle_cancel_order(
+ self, order: CcxtOrder, order_obj: Order, trade: Trade, reason: str
+ ) -> None:
"""
Check if current analyzed order timed out and cancel if necessary.
:param order: Order dict grabbed with exchange.fetch_order()
@@ -1602,7 +1612,7 @@ class FreqtradeBot(LoggingMixin):
self.emergency_exit(trade, order["price"], order["amount"])
def emergency_exit(
- self, trade: Trade, price: float, sub_trade_amt: Optional[float] = None
+ self, trade: Trade, price: float, sub_trade_amt: float | None = None
) -> None:
try:
self.execute_trade_exit(
@@ -1632,7 +1642,7 @@ class FreqtradeBot(LoggingMixin):
)
trade.delete()
- def replace_order(self, order: dict, order_obj: Optional[Order], trade: Trade) -> None:
+ def replace_order(self, order: CcxtOrder, order_obj: Order | None, trade: Trade) -> None:
"""
Check if current analyzed entry order should be replaced or simply cancelled.
To simply cancel the existing order(no replacement) adjust_entry_price() should return None
@@ -1736,10 +1746,10 @@ class FreqtradeBot(LoggingMixin):
def handle_cancel_enter(
self,
trade: Trade,
- order: dict,
+ order: CcxtOrder,
order_obj: Order,
reason: str,
- replacing: Optional[bool] = False,
+ replacing: bool | None = False,
) -> bool:
"""
entry cancel - cancel order
@@ -1820,7 +1830,9 @@ class FreqtradeBot(LoggingMixin):
)
return was_trade_fully_canceled
- def handle_cancel_exit(self, trade: Trade, order: dict, order_obj: Order, reason: str) -> bool:
+ def handle_cancel_exit(
+ self, trade: Trade, order: CcxtOrder, order_obj: Order, reason: str
+ ) -> bool:
"""
exit order cancel - cancel order and update trade
:return: True if exit order was cancelled, false otherwise
@@ -1931,9 +1943,9 @@ class FreqtradeBot(LoggingMixin):
limit: float,
exit_check: ExitCheckTuple,
*,
- exit_tag: Optional[str] = None,
- ordertype: Optional[str] = None,
- sub_trade_amt: Optional[float] = None,
+ exit_tag: str | None = None,
+ ordertype: str | None = None,
+ sub_trade_amt: float | None = None,
) -> bool:
"""
Executes a trade exit for the given trade and limit
@@ -2042,10 +2054,10 @@ class FreqtradeBot(LoggingMixin):
def _notify_exit(
self,
trade: Trade,
- order_type: Optional[str],
+ order_type: str | None,
fill: bool = False,
sub_trade: bool = False,
- order: Optional[Order] = None,
+ order: Order | None = None,
) -> None:
"""
Sends rpc notification when a sell occurred.
@@ -2158,7 +2170,7 @@ class FreqtradeBot(LoggingMixin):
# Send the message
self.rpc.send_msg(msg)
- def order_obj_or_raise(self, order_id: str, order_obj: Optional[Order]) -> Order:
+ def order_obj_or_raise(self, order_id: str, order_obj: Order | None) -> Order:
if not order_obj:
raise DependencyException(
f"Order_obj not found for {order_id}. This should not have happened."
@@ -2172,8 +2184,8 @@ class FreqtradeBot(LoggingMixin):
def update_trade_state(
self,
trade: Trade,
- order_id: Optional[str],
- action_order: Optional[dict[str, Any]] = None,
+ order_id: str | None,
+ action_order: CcxtOrder | None = None,
*,
stoploss_order: bool = False,
send_msg: bool = True,
@@ -2284,7 +2296,7 @@ class FreqtradeBot(LoggingMixin):
def handle_protections(self, pair: str, side: LongShort) -> None:
# Lock pair for one candle to prevent immediate re-entries
- self.strategy.lock_pair(pair, datetime.now(timezone.utc), reason="Auto lock")
+ self.strategy.lock_pair(pair, datetime.now(timezone.utc), reason="Auto lock", side=side)
prot_trig = self.protections.stop_per_pair(pair, side=side)
if prot_trig:
msg: RPCProtectionMsg = {
@@ -2310,7 +2322,7 @@ class FreqtradeBot(LoggingMixin):
amount: float,
fee_abs: float,
order_obj: Order,
- ) -> Optional[float]:
+ ) -> float | None:
"""
Applies the fee to amount (either from Order or from Trades).
Can eat into dust if more than the required asset is available.
@@ -2338,7 +2350,7 @@ class FreqtradeBot(LoggingMixin):
return fee_abs
return None
- def handle_order_fee(self, trade: Trade, order_obj: Order, order: dict[str, Any]) -> None:
+ def handle_order_fee(self, trade: Trade, order_obj: Order, order: CcxtOrder) -> None:
# Try update amount (binance-fix)
try:
fee_abs = self.get_real_amount(trade, order, order_obj)
@@ -2347,7 +2359,7 @@ class FreqtradeBot(LoggingMixin):
except DependencyException as exception:
logger.warning("Could not update trade amount: %s", exception)
- def get_real_amount(self, trade: Trade, order: dict, order_obj: Order) -> Optional[float]:
+ def get_real_amount(self, trade: Trade, order: CcxtOrder, order_obj: Order) -> float | None:
"""
Detect and update trade fee.
Calls trade.update_fee() upon correct detection.
@@ -2407,8 +2419,8 @@ class FreqtradeBot(LoggingMixin):
return True
def fee_detection_from_trades(
- self, trade: Trade, order: dict, order_obj: Order, order_amount: float, trades: list
- ) -> Optional[float]:
+ self, trade: Trade, order: CcxtOrder, order_obj: Order, order_amount: float, trades: list
+ ) -> float | None:
"""
fee-detection fallback to Trades.
Either uses provided trades list or the result of fetch_my_trades to get correct fee.
diff --git a/freqtrade/ft_types/backtest_result_type.py b/freqtrade/ft_types/backtest_result_type.py
index 25a890349..27cdb6126 100644
--- a/freqtrade/ft_types/backtest_result_type.py
+++ b/freqtrade/ft_types/backtest_result_type.py
@@ -1,4 +1,4 @@
-from typing import Any, Optional
+from typing import Any
from typing_extensions import TypedDict
@@ -26,7 +26,7 @@ class BacktestHistoryEntryType(BacktestMetadataType):
filename: str
strategy: str
notes: str
- backtest_start_ts: Optional[int]
- backtest_end_ts: Optional[int]
- timeframe: Optional[str]
- timeframe_detail: Optional[str]
+ backtest_start_ts: int | None
+ backtest_end_ts: int | None
+ timeframe: str | None
+ timeframe_detail: str | None
diff --git a/freqtrade/ft_types/valid_exchanges_type.py b/freqtrade/ft_types/valid_exchanges_type.py
index 89a06ba40..d6b93f4b2 100644
--- a/freqtrade/ft_types/valid_exchanges_type.py
+++ b/freqtrade/ft_types/valid_exchanges_type.py
@@ -1,5 +1,4 @@
# Used for list-exchanges
-from typing import Optional
from typing_extensions import TypedDict
@@ -17,5 +16,5 @@ class ValidExchangesType(TypedDict):
comment: str
dex: bool
is_alias: bool
- alias_for: Optional[str]
+ alias_for: str | None
trade_modes: list[TradeModeType]
diff --git a/freqtrade/leverage/liquidation_price.py b/freqtrade/leverage/liquidation_price.py
index af6ef0d44..aed8c9160 100644
--- a/freqtrade/leverage/liquidation_price.py
+++ b/freqtrade/leverage/liquidation_price.py
@@ -1,5 +1,4 @@
import logging
-from typing import Optional
from freqtrade.enums import MarginMode
from freqtrade.exceptions import DependencyException
@@ -12,7 +11,7 @@ logger = logging.getLogger(__name__)
def update_liquidation_prices(
- trade: Optional[LocalTrade] = None,
+ trade: LocalTrade | None = None,
*,
exchange: Exchange,
wallets: Wallets,
diff --git a/freqtrade/main.py b/freqtrade/main.py
index 712cc49cf..bd815b746 100755
--- a/freqtrade/main.py
+++ b/freqtrade/main.py
@@ -6,11 +6,11 @@ Read the documentation to know what cli arguments you need.
import logging
import sys
-from typing import Any, Optional
+from typing import Any
# check min. python version
-if sys.version_info < (3, 10): # pragma: no cover
+if sys.version_info < (3, 10): # pragma: no cover # noqa: UP036
sys.exit("Freqtrade requires Python version >= 3.10")
from freqtrade import __version__
@@ -24,7 +24,7 @@ from freqtrade.system import asyncio_setup, gc_set_threshold
logger = logging.getLogger("freqtrade")
-def main(sysargv: Optional[list[str]] = None) -> None:
+def main(sysargv: list[str] | None = None) -> None:
"""
This function will initiate the bot and start the trading loop.
:return: None
diff --git a/freqtrade/misc.py b/freqtrade/misc.py
index 629bd10dd..5ea227984 100644
--- a/freqtrade/misc.py
+++ b/freqtrade/misc.py
@@ -7,7 +7,7 @@ import logging
from collections.abc import Iterator, Mapping
from io import StringIO
from pathlib import Path
-from typing import Any, Optional, TextIO, Union
+from typing import Any, TextIO
from urllib.parse import urlparse
import pandas as pd
@@ -129,10 +129,10 @@ def round_dict(d, n):
return {k: (round(v, n) if isinstance(v, float) else v) for k, v in d.items()}
-DictMap = Union[dict[str, Any], Mapping[str, Any]]
+DictMap = dict[str, Any] | Mapping[str, Any]
-def safe_value_fallback(obj: DictMap, key1: str, key2: Optional[str] = None, default_value=None):
+def safe_value_fallback(obj: DictMap, key1: str, key2: str | None = None, default_value=None):
"""
Search a value in obj, return this if it's not None.
Then search key2 in obj - return that if it's not none - then use default_value.
@@ -161,7 +161,7 @@ def safe_value_fallback2(dict1: DictMap, dict2: DictMap, key1: str, key2: str, d
return default_value
-def plural(num: float, singular: str, plural: Optional[str] = None) -> str:
+def plural(num: float, singular: str, plural: str | None = None) -> str:
return singular if (num == 1 or num == -1) else plural or singular + "s"
diff --git a/freqtrade/mixins/logging_mixin.py b/freqtrade/mixins/logging_mixin.py
index 44fb4f63a..58399e738 100644
--- a/freqtrade/mixins/logging_mixin.py
+++ b/freqtrade/mixins/logging_mixin.py
@@ -1,4 +1,4 @@
-from typing import Callable
+from collections.abc import Callable
from cachetools import TTLCache, cached
diff --git a/freqtrade/optimize/analysis/lookahead_helpers.py b/freqtrade/optimize/analysis/lookahead_helpers.py
index dccf2cb73..d664f9635 100644
--- a/freqtrade/optimize/analysis/lookahead_helpers.py
+++ b/freqtrade/optimize/analysis/lookahead_helpers.py
@@ -1,7 +1,7 @@
import logging
import time
from pathlib import Path
-from typing import Any, Union
+from typing import Any
import pandas as pd
from rich.text import Text
@@ -21,7 +21,7 @@ class LookaheadAnalysisSubFunctions:
def text_table_lookahead_analysis_instances(
config: dict[str, Any],
lookahead_instances: list[LookaheadAnalysis],
- caption: Union[str, None] = None,
+ caption: str | None = None,
):
headers = [
"filename",
@@ -243,7 +243,7 @@ class LookaheadAnalysisSubFunctions:
# report the results
if lookaheadAnalysis_instances:
- caption: Union[str, None] = None
+ caption: str | None = None
if any(
[
any(
diff --git a/freqtrade/optimize/backtest_caching.py b/freqtrade/optimize/backtest_caching.py
index 766c77ddc..7143da006 100644
--- a/freqtrade/optimize/backtest_caching.py
+++ b/freqtrade/optimize/backtest_caching.py
@@ -1,7 +1,6 @@
import hashlib
from copy import deepcopy
from pathlib import Path
-from typing import Union
import rapidjson
@@ -38,7 +37,7 @@ def get_strategy_run_id(strategy) -> str:
return digest.hexdigest().lower()
-def get_backtest_metadata_filename(filename: Union[Path, str]) -> Path:
+def get_backtest_metadata_filename(filename: Path | str) -> Path:
"""Return metadata filename for specified backtest results file."""
filename = Path(filename)
return filename.parent / Path(f"{filename.stem}.meta{filename.suffix}")
diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py
index 03c7bcccc..18c7288dd 100644
--- a/freqtrade/optimize/backtesting.py
+++ b/freqtrade/optimize/backtesting.py
@@ -8,7 +8,7 @@ import logging
from collections import defaultdict
from copy import deepcopy
from datetime import datetime, timedelta, timezone
-from typing import Any, Optional
+from typing import Any
from numpy import nan
from pandas import DataFrame
@@ -110,7 +110,7 @@ class Backtesting:
backtesting.start()
"""
- def __init__(self, config: Config, exchange: Optional[Exchange] = None) -> None:
+ def __init__(self, config: Config, exchange: Exchange | None = None) -> None:
LoggingMixin.show_output = False
self.config = config
self.results: BacktestResultType = get_BacktestResultType_default()
@@ -685,7 +685,7 @@ class Backtesting:
)
def _try_close_open_order(
- self, order: Optional[Order], trade: LocalTrade, current_date: datetime, row: tuple
+ self, order: Order | None, trade: LocalTrade, current_date: datetime, row: tuple
) -> bool:
"""
Check if an order is open and if it should've filled.
@@ -732,7 +732,6 @@ class Backtesting:
trade.close_date = current_time
trade.close(order.ft_price, show_msg=False)
- # logger.debug(f"{pair} - Backtesting exit {trade}")
LocalTrade.close_bt_trade(trade)
self.wallets.update()
self.run_protections(pair, current_time, trade.trade_direction)
@@ -743,8 +742,8 @@ class Backtesting:
row: tuple,
exit_: ExitCheckTuple,
current_time: datetime,
- amount: Optional[float] = None,
- ) -> Optional[LocalTrade]:
+ amount: float | None = None,
+ ) -> LocalTrade | None:
if exit_.exit_flag:
trade.close_date = current_time
exit_reason = exit_.exit_reason
@@ -823,8 +822,8 @@ class Backtesting:
sell_row: tuple,
close_rate: float,
amount: float,
- exit_reason: Optional[str],
- ) -> Optional[LocalTrade]:
+ exit_reason: str | None,
+ ) -> LocalTrade | None:
self.order_id_counter += 1
exit_candle_time = sell_row[DATE_IDX].to_pydatetime()
order_type = self.strategy.order_types["exit"]
@@ -860,7 +859,7 @@ class Backtesting:
def _check_trade_exit(
self, trade: LocalTrade, row: tuple, current_time: datetime
- ) -> Optional[LocalTrade]:
+ ) -> LocalTrade | None:
self._run_funding_fees(trade, current_time)
# Check if we need to adjust our current positions
@@ -910,10 +909,10 @@ class Backtesting:
stake_amount: float,
direction: LongShort,
current_time: datetime,
- entry_tag: Optional[str],
- trade: Optional[LocalTrade],
+ entry_tag: str | None,
+ trade: LocalTrade | None,
order_type: str,
- price_precision: Optional[float],
+ price_precision: float | None,
) -> tuple[float, float, float, float]:
if order_type == "limit":
new_rate = strategy_safe_wrapper(
@@ -1005,12 +1004,12 @@ class Backtesting:
pair: str,
row: tuple,
direction: LongShort,
- stake_amount: Optional[float] = None,
- trade: Optional[LocalTrade] = None,
- requested_rate: Optional[float] = None,
- requested_stake: Optional[float] = None,
- entry_tag1: Optional[str] = None,
- ) -> Optional[LocalTrade]:
+ stake_amount: float | None = None,
+ trade: LocalTrade | None = None,
+ requested_rate: float | None = None,
+ requested_stake: float | None = None,
+ entry_tag1: str | None = None,
+ ) -> LocalTrade | None:
"""
:param trade: Trade to adjust - initial entry if None
:param requested_rate: Adjusted entry rate
@@ -1103,6 +1102,7 @@ class Backtesting:
fee_close=self.fee,
is_open=True,
enter_tag=entry_tag,
+ timeframe=self.timeframe_min,
exchange=self._exchange_name,
is_short=is_short,
trading_mode=self.trading_mode,
@@ -1178,7 +1178,7 @@ class Backtesting:
self.rejected_trades += 1
return False
- def check_for_trade_entry(self, row) -> Optional[LongShort]:
+ def check_for_trade_entry(self, row) -> LongShort | None:
enter_long = row[LONG_IDX] == 1
exit_long = row[ELONG_IDX] == 1
enter_short = self._can_short and row[SHORT_IDX] == 1
@@ -1216,7 +1216,7 @@ class Backtesting:
def check_order_cancel(
self, trade: LocalTrade, order: Order, current_time: datetime
- ) -> Optional[bool]:
+ ) -> bool | None:
"""
Check if current analyzed order has to be canceled.
Returns True if the trade should be Deleted (initial order was canceled),
@@ -1298,7 +1298,7 @@ class Backtesting:
def validate_row(
self, data: dict, pair: str, row_index: int, current_time: datetime
- ) -> Optional[tuple]:
+ ) -> tuple | None:
try:
# Row is treated as "current incomplete candle".
# entry / exit signals are shifted by 1 to compensate for this.
@@ -1332,14 +1332,41 @@ class Backtesting:
row: tuple,
pair: str,
current_time: datetime,
- trade_dir: Optional[LongShort],
+ trade_dir: LongShort | None,
can_enter: bool,
) -> None:
+ """
+ Conditionally call backtest_loop_inner a 2nd time if shorting is enabled,
+ a position closed and a new signal in the other direction is available.
+ """
+ if not self._can_short or trade_dir is None:
+ # No need to reverse position if shorting is disabled or there's no new signal
+ self.backtest_loop_inner(row, pair, current_time, trade_dir, can_enter)
+ else:
+ for _ in (0, 1):
+ a = self.backtest_loop_inner(row, pair, current_time, trade_dir, can_enter)
+ if not a or a == trade_dir:
+ # the trade didn't close or position change is in the same direction
+ break
+
+ def backtest_loop_inner(
+ self,
+ row: tuple,
+ pair: str,
+ current_time: datetime,
+ trade_dir: LongShort | None,
+ can_enter: bool,
+ ) -> LongShort | None:
"""
NOTE: This method is used by Hyperopt at each iteration. Please keep it optimized.
Backtesting processing for one candle/pair.
"""
+ exiting_dir: LongShort | None = None
+ if not self._position_stacking and len(LocalTrade.bt_trades_open_pp[pair]) > 0:
+ # position_stacking not supported for now.
+ exiting_dir = "short" if LocalTrade.bt_trades_open_pp[pair][0].is_short else "long"
+
for t in list(LocalTrade.bt_trades_open_pp[pair]):
# 1. Manage currently open orders of active trades
if self.manage_open_orders(t, current_time, row):
@@ -1358,7 +1385,7 @@ class Backtesting:
and (self._position_stacking or len(LocalTrade.bt_trades_open_pp[pair]) == 0)
and not PairLocks.is_pair_locked(pair, row[DATE_IDX], trade_dir)
):
- if self.trade_slot_available(LocalTrade.bt_open_open_trade_count):
+ if self.trade_slot_available(LocalTrade.bt_open_open_trade_count_candle):
trade = self._enter_trade(pair, row, trade_dir)
if trade:
self.wallets.update()
@@ -1380,6 +1407,10 @@ class Backtesting:
if order:
self._process_exit_order(order, trade, current_time, row, pair)
+ if exiting_dir and len(LocalTrade.bt_trades_open_pp[pair]) == 0:
+ return exiting_dir
+ return None
+
def time_pair_generator(
self, start_date: datetime, end_date: datetime, increment: timedelta, pairs: list[str]
):
@@ -1432,6 +1463,10 @@ class Backtesting:
):
if is_first_call:
self.check_abort()
+ # Reset open trade count for this candle
+ # Critical to avoid exceeding max_open_trades in backtesting
+ # when timeframe-detail is used and trades close within the opening candle.
+ LocalTrade.bt_open_open_trade_count_candle = LocalTrade.bt_open_open_trade_count
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)(
current_time=current_time
)
@@ -1446,7 +1481,7 @@ class Backtesting:
self.dataprovider._set_dataframe_max_index(self.required_startup + row_index)
self.dataprovider._set_dataframe_max_date(current_time)
current_detail_time: datetime = row[DATE_IDX].to_pydatetime()
- trade_dir: Optional[LongShort] = self.check_for_trade_entry(row)
+ trade_dir: LongShort | None = self.check_for_trade_entry(row)
if (
(trade_dir is not None or len(LocalTrade.bt_trades_open_pp[pair]) > 0)
@@ -1517,12 +1552,6 @@ class Backtesting:
backtest_start_time = datetime.now(timezone.utc)
self._set_strategy(strat)
- # Use max_open_trades in backtesting, except --disable-max-market-positions is set
- if not self.config.get("use_max_market_positions", True):
- logger.info("Ignoring max_open_trades (--disable-max-market-positions was used) ...")
- self.strategy.max_open_trades = float("inf")
- self.config.update({"max_open_trades": self.strategy.max_open_trades})
-
# need to reprocess data every time to populate signals
preprocessed = self.strategy.advise_all_indicators(data)
diff --git a/freqtrade/optimize/base_analysis.py b/freqtrade/optimize/base_analysis.py
index e3e475742..a2fe53867 100644
--- a/freqtrade/optimize/base_analysis.py
+++ b/freqtrade/optimize/base_analysis.py
@@ -1,7 +1,7 @@
import logging
from copy import deepcopy
from datetime import datetime, timezone
-from typing import Any, Optional
+from typing import Any
from pandas import DataFrame
@@ -28,7 +28,7 @@ class BaseAnalysis:
def __init__(self, config: dict[str, Any], strategy_obj: dict):
self.failed_bias_check = True
self.full_varHolder = VarHolder()
- self.exchange: Optional[Any] = None
+ self.exchange: Any | None = None
self._fee = None
# pull variables the scope of the lookahead_analysis-instance
diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py
index daa4661ad..0f46ee1e9 100644
--- a/freqtrade/optimize/hyperopt.py
+++ b/freqtrade/optimize/hyperopt.py
@@ -11,7 +11,7 @@ import warnings
from datetime import datetime, timezone
from math import ceil
from pathlib import Path
-from typing import Any, Optional
+from typing import Any
import rapidjson
from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects
@@ -125,13 +125,7 @@ class Hyperopt:
self.market_change = 0.0
self.num_epochs_saved = 0
- self.current_best_epoch: Optional[dict[str, Any]] = None
-
- # Use max_open_trades for hyperopt as well, except --disable-max-market-positions is set
- if not self.config.get("use_max_market_positions", True):
- logger.debug("Ignoring max_open_trades (--disable-max-market-positions was used) ...")
- self.backtesting.strategy.max_open_trades = float("inf")
- config.update({"max_open_trades": self.backtesting.strategy.max_open_trades})
+ self.current_best_epoch: dict[str, Any] | None = None
if HyperoptTools.has_space(self.config, "sell"):
# Make sure use_exit_signal is enabled
@@ -177,7 +171,7 @@ class Hyperopt:
# Return a dict where the keys are the names of the dimensions
# and the values are taken from the list of parameters.
- return {d.name: v for d, v in zip(dimensions, raw_params)}
+ return {d.name: v for d, v in zip(dimensions, raw_params, strict=False)}
def _save_result(self, epoch: dict) -> None:
"""
@@ -485,7 +479,7 @@ class Hyperopt:
delayed(wrap_non_picklable_objects(self.generate_optimizer))(v) for v in asked
)
- def _set_random_state(self, random_state: Optional[int]) -> int:
+ def _set_random_state(self, random_state: int | None) -> int:
return random_state or random.randint(1, 2**16 - 1) # noqa: S311
def advise_and_trim(self, data: dict[str, DataFrame]) -> dict[str, DataFrame]:
@@ -557,7 +551,7 @@ class Hyperopt:
is_random = [True for _ in range(len(asked))]
is_random_non_tried += [
rand
- for x, rand in zip(asked, is_random)
+ for x, rand in zip(asked, is_random, strict=False)
if x not in self.opt.Xi and x not in asked_non_tried
]
asked_non_tried += [
diff --git a/freqtrade/optimize/hyperopt_auto.py b/freqtrade/optimize/hyperopt_auto.py
index 9d2f89dc7..702cff51d 100644
--- a/freqtrade/optimize/hyperopt_auto.py
+++ b/freqtrade/optimize/hyperopt_auto.py
@@ -5,8 +5,8 @@ This module implements a convenience auto-hyperopt class, which can be used toge
"""
import logging
+from collections.abc import Callable
from contextlib import suppress
-from typing import Callable
from freqtrade.exceptions import OperationalException
diff --git a/freqtrade/optimize/hyperopt_interface.py b/freqtrade/optimize/hyperopt_interface.py
index 2c0983b52..6382d7f8d 100644
--- a/freqtrade/optimize/hyperopt_interface.py
+++ b/freqtrade/optimize/hyperopt_interface.py
@@ -6,7 +6,7 @@ This module defines the interface to apply for hyperopt
import logging
import math
from abc import ABC
-from typing import Union
+from typing import TypeAlias
from sklearn.base import RegressorMixin
from skopt.space import Categorical, Dimension, Integer
@@ -20,7 +20,7 @@ from freqtrade.strategy import IStrategy
logger = logging.getLogger(__name__)
-EstimatorType = Union[RegressorMixin, str]
+EstimatorType: TypeAlias = RegressorMixin | str
class IHyperOpt(ABC):
diff --git a/freqtrade/optimize/hyperopt_output.py b/freqtrade/optimize/hyperopt_output.py
index c30715046..5327bffc1 100644
--- a/freqtrade/optimize/hyperopt_output.py
+++ b/freqtrade/optimize/hyperopt_output.py
@@ -1,6 +1,6 @@
import sys
from os import get_terminal_size
-from typing import Any, Optional
+from typing import Any
from rich.align import Align
from rich.console import Console
@@ -37,7 +37,7 @@ class HyperoptOutput:
self.table.add_column("Objective", justify="right")
self.table.add_column("Max Drawdown (Acct)", justify="right")
- def print(self, console: Optional[Console] = None, *, print_colorized=True):
+ def print(self, console: Console | None = None, *, print_colorized=True):
if not console:
console = Console(
color_system="auto" if print_colorized else None,
@@ -57,7 +57,7 @@ class HyperoptOutput:
stake_currency = config["stake_currency"]
self._results.extend(results)
- max_rows: Optional[int] = None
+ max_rows: int | None = None
if self._streaming:
try:
diff --git a/freqtrade/optimize/hyperopt_tools.py b/freqtrade/optimize/hyperopt_tools.py
index 66e194510..964afc2ab 100644
--- a/freqtrade/optimize/hyperopt_tools.py
+++ b/freqtrade/optimize/hyperopt_tools.py
@@ -3,7 +3,7 @@ from collections.abc import Iterator
from copy import deepcopy
from datetime import datetime, timezone
from pathlib import Path
-from typing import Any, Optional
+from typing import Any
import numpy as np
import rapidjson
@@ -44,7 +44,7 @@ class HyperoptStateContainer:
class HyperoptTools:
@staticmethod
- def get_strategy_filename(config: Config, strategy_name: str) -> Optional[Path]:
+ def get_strategy_filename(config: Config, strategy_name: str) -> Path | None:
"""
Get Strategy-location (filename) from strategy_name
"""
@@ -188,7 +188,7 @@ class HyperoptTools:
total_epochs: int,
print_json: bool,
no_header: bool = False,
- header_str: Optional[str] = None,
+ header_str: str | None = None,
) -> None:
"""
Display details of the hyperopt result
@@ -257,7 +257,7 @@ class HyperoptTools:
@staticmethod
def _params_pretty_print(
- params, space: str, header: str, non_optimized: Optional[dict] = None
+ params, space: str, header: str, non_optimized: dict | None = None
) -> None:
if space in params or (non_optimized and space in non_optimized):
space_params = HyperoptTools._space_params(params, space, 5)
@@ -299,7 +299,7 @@ class HyperoptTools:
print(result)
@staticmethod
- def _space_params(params, space: str, r: Optional[int] = None) -> dict:
+ def _space_params(params, space: str, r: int | None = None) -> dict:
d = params.get(space)
if d:
# Round floats to `r` digits after the decimal point if requested
diff --git a/freqtrade/optimize/optimize_reports/bt_output.py b/freqtrade/optimize/optimize_reports/bt_output.py
index a1dd73358..4fdc03d7b 100644
--- a/freqtrade/optimize/optimize_reports/bt_output.py
+++ b/freqtrade/optimize/optimize_reports/bt_output.py
@@ -1,5 +1,5 @@
import logging
-from typing import Any, Literal, Union
+from typing import Any, Literal
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT, Config
from freqtrade.ft_types import BacktestResultType
@@ -18,7 +18,7 @@ def _get_line_floatfmt(stake_currency: str) -> list[str]:
def _get_line_header(
- first_column: Union[str, list[str]], stake_currency: str, direction: str = "Trades"
+ first_column: str | list[str], stake_currency: str, direction: str = "Trades"
) -> list[str]:
"""
Generate header lines (goes in line with _generate_result_line())
@@ -172,7 +172,7 @@ def text_table_strategy(strategy_results, stake_currency: str, title: str):
dd_pad_per = max([len(dd) for dd in drawdown])
drawdown = [
f'{t["max_drawdown_abs"]:>{dd_pad_abs}} {stake_currency} {dd:>{dd_pad_per}}%'
- for t, dd in zip(strategy_results, drawdown)
+ for t, dd in zip(strategy_results, drawdown, strict=False)
]
output = [
@@ -186,7 +186,7 @@ def text_table_strategy(strategy_results, stake_currency: str, title: str):
generate_wins_draws_losses(t["wins"], t["draws"], t["losses"]),
drawdown,
]
- for t, drawdown in zip(strategy_results, drawdown)
+ for t, drawdown in zip(strategy_results, drawdown, strict=False)
]
print_rich_table(output, headers, summary=title)
diff --git a/freqtrade/optimize/optimize_reports/bt_storage.py b/freqtrade/optimize/optimize_reports/bt_storage.py
index 9633d9c18..21532d4d2 100644
--- a/freqtrade/optimize/optimize_reports/bt_storage.py
+++ b/freqtrade/optimize/optimize_reports/bt_storage.py
@@ -1,6 +1,5 @@
import logging
from pathlib import Path
-from typing import Optional
from pandas import DataFrame
@@ -35,7 +34,7 @@ def store_backtest_stats(
stats: BacktestResultType,
dtappendix: str,
*,
- market_change_data: Optional[DataFrame] = None,
+ market_change_data: DataFrame | None = None,
) -> Path:
"""
Stores backtest results
diff --git a/freqtrade/optimize/optimize_reports/optimize_reports.py b/freqtrade/optimize/optimize_reports/optimize_reports.py
index 9650bdde6..8f1f0140a 100644
--- a/freqtrade/optimize/optimize_reports/optimize_reports.py
+++ b/freqtrade/optimize/optimize_reports/optimize_reports.py
@@ -1,7 +1,7 @@
import logging
from copy import deepcopy
from datetime import datetime, timedelta, timezone
-from typing import Any, Literal, Union
+from typing import Any, Literal
import numpy as np
from pandas import DataFrame, Series, concat, to_datetime
@@ -69,7 +69,7 @@ def generate_rejected_signals(
def _generate_result_line(
- result: DataFrame, starting_balance: int, first_column: Union[str, list[str]]
+ result: DataFrame, starting_balance: int, first_column: str | list[str]
) -> dict:
"""
Generate one result dict, with "first_column" as key.
@@ -143,7 +143,7 @@ def generate_pair_metrics(
def generate_tag_metrics(
- tag_type: Union[Literal["enter_tag", "exit_reason"], list[Literal["enter_tag", "exit_reason"]]],
+ tag_type: Literal["enter_tag", "exit_reason"] | list[Literal["enter_tag", "exit_reason"]],
starting_balance: int,
results: DataFrame,
skip_nan: bool = False,
@@ -208,7 +208,7 @@ def _get_resample_from_period(period: str) -> str:
def generate_periodic_breakdown_stats(
- trade_list: Union[list, DataFrame], period: str
+ trade_list: list | DataFrame, period: str
) -> list[dict[str, Any]]:
results = trade_list if not isinstance(trade_list, list) else DataFrame.from_records(trade_list)
if len(results) == 0:
@@ -559,7 +559,7 @@ def generate_strategy_stats(
def generate_backtest_stats(
btdata: dict[str, DataFrame],
- all_results: dict[str, dict[str, Union[DataFrame, dict]]],
+ all_results: dict[str, dict[str, DataFrame | dict]],
min_date: datetime,
max_date: datetime,
) -> BacktestResultType:
diff --git a/freqtrade/persistence/custom_data.py b/freqtrade/persistence/custom_data.py
index 3a2e700fb..17c0cbf0a 100644
--- a/freqtrade/persistence/custom_data.py
+++ b/freqtrade/persistence/custom_data.py
@@ -2,7 +2,7 @@ import json
import logging
from collections.abc import Sequence
from datetime import datetime
-from typing import Any, ClassVar, Optional
+from typing import Any, ClassVar
from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, UniqueConstraint, select
from sqlalchemy.orm import Mapped, mapped_column, relationship
@@ -42,7 +42,7 @@ class _CustomData(ModelBase):
cd_type: Mapped[str] = mapped_column(String(25), nullable=False)
cd_value: Mapped[str] = mapped_column(Text, nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=dt_now)
- updated_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
+ updated_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
# Empty container value - not persisted, but filled with cd_value on query
value: Any = None
@@ -62,7 +62,7 @@ class _CustomData(ModelBase):
@classmethod
def query_cd(
- cls, key: Optional[str] = None, trade_id: Optional[int] = None
+ cls, key: str | None = None, trade_id: int | None = None
) -> Sequence["_CustomData"]:
"""
Get all CustomData, if trade_id is not specified
@@ -117,7 +117,7 @@ class CustomDataWrapper:
_CustomData.session.commit()
@staticmethod
- def get_custom_data(*, trade_id: int, key: Optional[str] = None) -> list[_CustomData]:
+ def get_custom_data(*, trade_id: int, key: str | None = None) -> list[_CustomData]:
if CustomDataWrapper.use_db:
filters = [
_CustomData.ft_trade_id == trade_id,
diff --git a/freqtrade/persistence/key_value_store.py b/freqtrade/persistence/key_value_store.py
index 93960a102..e7b782ac4 100644
--- a/freqtrade/persistence/key_value_store.py
+++ b/freqtrade/persistence/key_value_store.py
@@ -1,6 +1,6 @@
from datetime import datetime, timezone
from enum import Enum
-from typing import ClassVar, Optional, Union
+from typing import ClassVar
from sqlalchemy import String
from sqlalchemy.orm import Mapped, mapped_column
@@ -8,7 +8,7 @@ from sqlalchemy.orm import Mapped, mapped_column
from freqtrade.persistence.base import ModelBase, SessionType
-ValueTypes = Union[str, datetime, float, int]
+ValueTypes = str | datetime | float | int
class ValueTypesEnum(str, Enum):
@@ -37,10 +37,10 @@ class _KeyValueStoreModel(ModelBase):
value_type: Mapped[ValueTypesEnum] = mapped_column(String(20), nullable=False)
- string_value: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
- datetime_value: Mapped[Optional[datetime]]
- float_value: Mapped[Optional[float]]
- int_value: Mapped[Optional[int]]
+ string_value: Mapped[str | None] = mapped_column(String(255), nullable=True)
+ datetime_value: Mapped[datetime | None]
+ float_value: Mapped[float | None]
+ int_value: Mapped[int | None]
class KeyValueStore:
@@ -97,7 +97,7 @@ class KeyValueStore:
_KeyValueStoreModel.session.commit()
@staticmethod
- def get_value(key: KeyStoreKeys) -> Optional[ValueTypes]:
+ def get_value(key: KeyStoreKeys) -> ValueTypes | None:
"""
Get the value for the given key.
:param key: Key to get the value for
@@ -121,7 +121,7 @@ class KeyValueStore:
raise ValueError(f"Unknown value type {kv.value_type}") # pragma: no cover
@staticmethod
- def get_string_value(key: KeyStoreKeys) -> Optional[str]:
+ def get_string_value(key: KeyStoreKeys) -> str | None:
"""
Get the value for the given key.
:param key: Key to get the value for
@@ -139,7 +139,7 @@ class KeyValueStore:
return kv.string_value
@staticmethod
- def get_datetime_value(key: KeyStoreKeys) -> Optional[datetime]:
+ def get_datetime_value(key: KeyStoreKeys) -> datetime | None:
"""
Get the value for the given key.
:param key: Key to get the value for
@@ -157,7 +157,7 @@ class KeyValueStore:
return kv.datetime_value.replace(tzinfo=timezone.utc)
@staticmethod
- def get_float_value(key: KeyStoreKeys) -> Optional[float]:
+ def get_float_value(key: KeyStoreKeys) -> float | None:
"""
Get the value for the given key.
:param key: Key to get the value for
@@ -175,7 +175,7 @@ class KeyValueStore:
return kv.float_value
@staticmethod
- def get_int_value(key: KeyStoreKeys) -> Optional[int]:
+ def get_int_value(key: KeyStoreKeys) -> int | None:
"""
Get the value for the given key.
:param key: Key to get the value for
diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py
index 1dd69493f..7a88c61c0 100644
--- a/freqtrade/persistence/migrations.py
+++ b/freqtrade/persistence/migrations.py
@@ -1,5 +1,4 @@
import logging
-from typing import Optional
from sqlalchemy import inspect, select, text, update
@@ -32,8 +31,8 @@ def get_backup_name(tabs: list[str], backup_prefix: str):
def get_last_sequence_ids(engine, trade_back_name: str, order_back_name: str):
- order_id: Optional[int] = None
- trade_id: Optional[int] = None
+ order_id: int | None = None
+ trade_id: int | None = None
if engine.name == "postgresql":
with engine.begin() as connection:
diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py
index 87ba3a2d2..bfcf83457 100644
--- a/freqtrade/persistence/models.py
+++ b/freqtrade/persistence/models.py
@@ -5,7 +5,7 @@ This module contains the class to persist trades into SQLite
import logging
import threading
from contextvars import ContextVar
-from typing import Any, Final, Optional
+from typing import Any, Final
from sqlalchemy import create_engine, inspect
from sqlalchemy.exc import NoSuchModuleError
@@ -25,10 +25,10 @@ logger = logging.getLogger(__name__)
REQUEST_ID_CTX_KEY: Final[str] = "request_id"
-_request_id_ctx_var: ContextVar[Optional[str]] = ContextVar(REQUEST_ID_CTX_KEY, default=None)
+_request_id_ctx_var: ContextVar[str | None] = ContextVar(REQUEST_ID_CTX_KEY, default=None)
-def get_request_or_thread_id() -> Optional[str]:
+def get_request_or_thread_id() -> str | None:
"""
Helper method to get either async context (for fastapi requests), or thread id
"""
diff --git a/freqtrade/persistence/pairlock.py b/freqtrade/persistence/pairlock.py
index caec2c78a..01549fb63 100644
--- a/freqtrade/persistence/pairlock.py
+++ b/freqtrade/persistence/pairlock.py
@@ -1,5 +1,5 @@
from datetime import datetime, timezone
-from typing import Any, ClassVar, Optional
+from typing import Any, ClassVar
from sqlalchemy import ScalarResult, String, or_, select
from sqlalchemy.orm import Mapped, mapped_column
@@ -21,7 +21,7 @@ class PairLock(ModelBase):
pair: Mapped[str] = mapped_column(String(25), nullable=False, index=True)
# lock direction - long, short or * (for both)
side: Mapped[str] = mapped_column(String(25), nullable=False, default="*")
- reason: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
+ reason: Mapped[str | None] = mapped_column(String(255), nullable=True)
# Time the pair was locked (start time)
lock_time: Mapped[datetime] = mapped_column(nullable=False)
# Time until the pair is locked (end time)
@@ -39,7 +39,7 @@ class PairLock(ModelBase):
@staticmethod
def query_pair_locks(
- pair: Optional[str], now: datetime, side: str = "*"
+ pair: str | None, now: datetime, side: str = "*"
) -> ScalarResult["PairLock"]:
"""
Get all currently active locks for this pair
diff --git a/freqtrade/persistence/pairlock_middleware.py b/freqtrade/persistence/pairlock_middleware.py
index a30592421..c182cf534 100644
--- a/freqtrade/persistence/pairlock_middleware.py
+++ b/freqtrade/persistence/pairlock_middleware.py
@@ -1,7 +1,6 @@
import logging
from collections.abc import Sequence
from datetime import datetime, timezone
-from typing import Optional
from sqlalchemy import select
@@ -36,9 +35,9 @@ class PairLocks:
def lock_pair(
pair: str,
until: datetime,
- reason: Optional[str] = None,
+ reason: str | None = None,
*,
- now: Optional[datetime] = None,
+ now: datetime | None = None,
side: str = "*",
) -> PairLock:
"""
@@ -68,7 +67,7 @@ class PairLocks:
@staticmethod
def get_pair_locks(
- pair: Optional[str], now: Optional[datetime] = None, side: str = "*"
+ pair: str | None, now: datetime | None = None, side: str = "*"
) -> Sequence[PairLock]:
"""
Get all currently active locks for this pair
@@ -96,8 +95,8 @@ class PairLocks:
@staticmethod
def get_pair_longest_lock(
- pair: str, now: Optional[datetime] = None, side: str = "*"
- ) -> Optional[PairLock]:
+ pair: str, now: datetime | None = None, side: str = "*"
+ ) -> PairLock | None:
"""
Get the lock that expires the latest for the pair given.
"""
@@ -106,7 +105,7 @@ class PairLocks:
return locks[0] if locks else None
@staticmethod
- def unlock_pair(pair: str, now: Optional[datetime] = None, side: str = "*") -> None:
+ def unlock_pair(pair: str, now: datetime | None = None, side: str = "*") -> None:
"""
Release all locks for this pair.
:param pair: Pair to unlock
@@ -124,7 +123,7 @@ class PairLocks:
PairLock.session.commit()
@staticmethod
- def unlock_reason(reason: str, now: Optional[datetime] = None) -> None:
+ def unlock_reason(reason: str, now: datetime | None = None) -> None:
"""
Release all locks for this reason.
:param reason: Which reason to unlock
@@ -155,7 +154,7 @@ class PairLocks:
lock.active = False
@staticmethod
- def is_global_lock(now: Optional[datetime] = None, side: str = "*") -> bool:
+ def is_global_lock(now: datetime | None = None, side: str = "*") -> bool:
"""
:param now: Datetime object (generated via datetime.now(timezone.utc)).
defaults to datetime.now(timezone.utc)
@@ -166,7 +165,7 @@ class PairLocks:
return len(PairLocks.get_pair_locks("*", now, side)) > 0
@staticmethod
- def is_pair_locked(pair: str, now: Optional[datetime] = None, side: str = "*") -> bool:
+ def is_pair_locked(pair: str, now: datetime | None = None, side: str = "*") -> bool:
"""
:param pair: Pair to check for
:param now: Datetime object (generated via datetime.now(timezone.utc)).
diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py
index afae5f4c7..e4979b438 100644
--- a/freqtrade/persistence/trade_model.py
+++ b/freqtrade/persistence/trade_model.py
@@ -43,6 +43,7 @@ from freqtrade.exchange import (
amount_to_contract_precision,
price_to_precision,
)
+from freqtrade.exchange.exchange_types import CcxtOrder
from freqtrade.leverage import interest
from freqtrade.misc import safe_value_fallback
from freqtrade.persistence.base import ModelBase, SessionType
@@ -96,26 +97,24 @@ class Order(ModelBase):
ft_cancel_reason: Mapped[str] = mapped_column(String(CUSTOM_TAG_MAX_LENGTH), nullable=True)
order_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True)
- status: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
- symbol: Mapped[Optional[str]] = mapped_column(String(25), nullable=True)
- order_type: Mapped[Optional[str]] = mapped_column(String(50), nullable=True)
+ status: Mapped[str | None] = mapped_column(String(255), nullable=True)
+ symbol: Mapped[str | None] = mapped_column(String(25), nullable=True)
+ order_type: Mapped[str | None] = mapped_column(String(50), nullable=True)
side: Mapped[str] = mapped_column(String(25), nullable=True)
- price: Mapped[Optional[float]] = mapped_column(Float(), nullable=True)
- average: Mapped[Optional[float]] = mapped_column(Float(), nullable=True)
- amount: Mapped[Optional[float]] = mapped_column(Float(), nullable=True)
- filled: Mapped[Optional[float]] = mapped_column(Float(), nullable=True)
- remaining: Mapped[Optional[float]] = mapped_column(Float(), nullable=True)
- cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True)
- stop_price: Mapped[Optional[float]] = mapped_column(Float(), nullable=True)
+ price: Mapped[float | None] = mapped_column(Float(), nullable=True)
+ average: Mapped[float | None] = mapped_column(Float(), nullable=True)
+ amount: Mapped[float | None] = mapped_column(Float(), nullable=True)
+ filled: Mapped[float | None] = mapped_column(Float(), nullable=True)
+ remaining: Mapped[float | None] = mapped_column(Float(), nullable=True)
+ cost: Mapped[float | None] = mapped_column(Float(), nullable=True)
+ stop_price: Mapped[float | None] = mapped_column(Float(), nullable=True)
order_date: Mapped[datetime] = mapped_column(nullable=True, default=dt_now)
- order_filled_date: Mapped[Optional[datetime]] = mapped_column(nullable=True)
- order_update_date: Mapped[Optional[datetime]] = mapped_column(nullable=True)
- funding_fee: Mapped[Optional[float]] = mapped_column(Float(), nullable=True)
+ order_filled_date: Mapped[datetime | None] = mapped_column(nullable=True)
+ order_update_date: Mapped[datetime | None] = mapped_column(nullable=True)
+ funding_fee: Mapped[float | None] = mapped_column(Float(), nullable=True)
- ft_fee_base: Mapped[Optional[float]] = mapped_column(Float(), nullable=True)
- ft_order_tag: Mapped[Optional[str]] = mapped_column(
- String(CUSTOM_TAG_MAX_LENGTH), nullable=True
- )
+ ft_fee_base: Mapped[float | None] = mapped_column(Float(), nullable=True)
+ ft_order_tag: Mapped[str | None] = mapped_column(String(CUSTOM_TAG_MAX_LENGTH), nullable=True)
@property
def order_date_utc(self) -> datetime:
@@ -123,7 +122,7 @@ class Order(ModelBase):
return self.order_date.replace(tzinfo=timezone.utc)
@property
- def order_filled_utc(self) -> Optional[datetime]:
+ def order_filled_utc(self) -> datetime | None:
"""last order-date with UTC timezoneinfo"""
return (
self.order_filled_date.replace(tzinfo=timezone.utc) if self.order_filled_date else None
@@ -175,6 +174,11 @@ class Order(ModelBase):
"""Amount in stake currency used for this order"""
return self.safe_amount * self.safe_price / self.trade.leverage
+ @property
+ def stake_amount_filled(self) -> float:
+ """Filled Amount in stake currency used for this order"""
+ return self.safe_filled * self.safe_price / self.trade.leverage
+
def __repr__(self):
return (
f"Order(id={self.id}, trade={self.ft_trade_id}, order_id={self.order_id}, "
@@ -309,7 +313,7 @@ class Order(ModelBase):
trade.adjust_stop_loss(trade.open_rate, trade.stop_loss_pct)
@staticmethod
- def update_orders(orders: list["Order"], order: dict[str, Any]):
+ def update_orders(orders: list["Order"], order: CcxtOrder):
"""
Get all non-closed orders - useful when trying to batch-update orders
"""
@@ -328,11 +332,11 @@ class Order(ModelBase):
@classmethod
def parse_from_ccxt_object(
cls,
- order: dict[str, Any],
+ order: CcxtOrder,
pair: str,
side: str,
- amount: Optional[float] = None,
- price: Optional[float] = None,
+ amount: float | None = None,
+ price: float | None = None,
) -> Self:
"""
Parse an order from a ccxt object and return a new order Object.
@@ -379,6 +383,7 @@ class LocalTrade:
# Copy of trades_open - but indexed by pair
bt_trades_open_pp: dict[str, list["LocalTrade"]] = defaultdict(list)
bt_open_open_trade_count: int = 0
+ bt_open_open_trade_count_candle: int = 0
bt_total_profit: float = 0
realized_profit: float = 0
@@ -388,57 +393,57 @@ class LocalTrade:
exchange: str = ""
pair: str = ""
- base_currency: Optional[str] = ""
- stake_currency: Optional[str] = ""
+ base_currency: str | None = ""
+ stake_currency: str | None = ""
is_open: bool = True
fee_open: float = 0.0
- fee_open_cost: Optional[float] = None
- fee_open_currency: Optional[str] = ""
- fee_close: Optional[float] = 0.0
- fee_close_cost: Optional[float] = None
- fee_close_currency: Optional[str] = ""
+ fee_open_cost: float | None = None
+ fee_open_currency: str | None = ""
+ fee_close: float | None = 0.0
+ fee_close_cost: float | None = None
+ fee_close_currency: str | None = ""
open_rate: float = 0.0
- open_rate_requested: Optional[float] = None
+ open_rate_requested: float | None = None
# open_trade_value - calculated via _calc_open_trade_value
open_trade_value: float = 0.0
- close_rate: Optional[float] = None
- close_rate_requested: Optional[float] = None
- close_profit: Optional[float] = None
- close_profit_abs: Optional[float] = None
+ close_rate: float | None = None
+ close_rate_requested: float | None = None
+ close_profit: float | None = None
+ close_profit_abs: float | None = None
stake_amount: float = 0.0
- max_stake_amount: Optional[float] = 0.0
+ max_stake_amount: float | None = 0.0
amount: float = 0.0
- amount_requested: Optional[float] = None
+ amount_requested: float | None = None
open_date: datetime
- close_date: Optional[datetime] = None
+ close_date: datetime | None = None
# absolute value of the stop loss
stop_loss: float = 0.0
# percentage value of the stop loss
- stop_loss_pct: Optional[float] = 0.0
+ stop_loss_pct: float | None = 0.0
# absolute value of the initial stop loss
- initial_stop_loss: Optional[float] = 0.0
+ initial_stop_loss: float | None = 0.0
# percentage value of the initial stop loss
- initial_stop_loss_pct: Optional[float] = None
+ initial_stop_loss_pct: float | None = None
is_stop_loss_trailing: bool = False
# absolute value of the highest reached price
- max_rate: Optional[float] = None
+ max_rate: float | None = None
# Lowest price reached
- min_rate: Optional[float] = None
- exit_reason: Optional[str] = ""
- exit_order_status: Optional[str] = ""
- strategy: Optional[str] = ""
- enter_tag: Optional[str] = None
- timeframe: Optional[int] = None
+ min_rate: float | None = None
+ exit_reason: str | None = ""
+ exit_order_status: str | None = ""
+ strategy: str | None = ""
+ enter_tag: str | None = None
+ timeframe: int | None = None
trading_mode: TradingMode = TradingMode.SPOT
- amount_precision: Optional[float] = None
- price_precision: Optional[float] = None
- precision_mode: Optional[int] = None
- precision_mode_price: Optional[int] = None
- contract_size: Optional[float] = None
+ amount_precision: float | None = None
+ price_precision: float | None = None
+ precision_mode: int | None = None
+ precision_mode_price: int | None = None
+ contract_size: float | None = None
# Leverage trading properties
- liquidation_price: Optional[float] = None
+ liquidation_price: float | None = None
is_short: bool = False
leverage: float = 1.0
@@ -446,10 +451,10 @@ class LocalTrade:
interest_rate: float = 0.0
# Futures properties
- funding_fees: Optional[float] = None
+ funding_fees: float | None = None
# Used to keep running funding fees - between the last filled order and now
# Shall not be used for calculations!
- funding_fee_running: Optional[float] = None
+ funding_fee_running: float | None = None
@property
def stoploss_or_liquidation(self) -> float:
@@ -462,7 +467,7 @@ class LocalTrade:
return self.stop_loss
@property
- def buy_tag(self) -> Optional[str]:
+ def buy_tag(self) -> str | None:
"""
Compatibility between buy_tag (old) and enter_tag (new)
Consider buy_tag deprecated
@@ -489,7 +494,7 @@ class LocalTrade:
return self.amount
@property
- def _date_last_filled_utc(self) -> Optional[datetime]:
+ def _date_last_filled_utc(self) -> datetime | None:
"""Date of the last filled order"""
orders = self.select_filled_orders()
if orders:
@@ -505,7 +510,7 @@ class LocalTrade:
return max([self.open_date_utc, dt_last_filled])
@property
- def date_entry_fill_utc(self) -> Optional[datetime]:
+ def date_entry_fill_utc(self) -> datetime | None:
"""Date of the first filled order"""
orders = self.select_filled_orders(self.entry_side)
if orders and len(
@@ -747,6 +752,7 @@ class LocalTrade:
LocalTrade.bt_trades_open = []
LocalTrade.bt_trades_open_pp = defaultdict(list)
LocalTrade.bt_open_open_trade_count = 0
+ LocalTrade.bt_open_open_trade_count_candle = 0
LocalTrade.bt_total_profit = 0
def adjust_min_max_rates(self, current_price: float, current_price_low: float) -> None:
@@ -756,7 +762,7 @@ class LocalTrade:
self.max_rate = max(current_price, self.max_rate or self.open_rate)
self.min_rate = min(current_price_low, self.min_rate or self.open_rate)
- def set_liquidation_price(self, liquidation_price: Optional[float]):
+ def set_liquidation_price(self, liquidation_price: float | None):
"""
Method you should use to set self.liquidation price.
Assures stop_loss is not passed the liquidation price
@@ -788,7 +794,7 @@ class LocalTrade:
def adjust_stop_loss(
self,
current_price: float,
- stoploss: Optional[float],
+ stoploss: float | None,
initial: bool = False,
allow_refresh: bool = False,
) -> None:
@@ -928,7 +934,7 @@ class LocalTrade:
)
def update_fee(
- self, fee_cost: float, fee_currency: Optional[str], fee_rate: Optional[float], side: str
+ self, fee_cost: float, fee_currency: str | None, fee_rate: float | None, side: str
) -> None:
"""
Update Fee parameters. Only acts once per side
@@ -957,7 +963,7 @@ class LocalTrade:
else:
return False
- def update_order(self, order: dict) -> None:
+ def update_order(self, order: CcxtOrder) -> None:
Order.update_orders(self.orders, order)
@property
@@ -1036,7 +1042,7 @@ class LocalTrade:
return interest(exchange_name=self.exchange, borrowed=borrowed, rate=rate, hours=hours)
- def _calc_base_close(self, amount: FtPrecise, rate: float, fee: Optional[float]) -> FtPrecise:
+ def _calc_base_close(self, amount: FtPrecise, rate: float, fee: float | None) -> FtPrecise:
close_trade = amount * FtPrecise(rate)
fees = close_trade * FtPrecise(fee or 0.0)
@@ -1045,7 +1051,7 @@ class LocalTrade:
else:
return close_trade - fees
- def calc_close_trade_value(self, rate: float, amount: Optional[float] = None) -> float:
+ def calc_close_trade_value(self, rate: float, amount: float | None = None) -> float:
"""
Calculate the Trade's close value including fees
:param rate: rate to compare with.
@@ -1084,7 +1090,7 @@ class LocalTrade:
)
def calc_profit(
- self, rate: float, amount: Optional[float] = None, open_rate: Optional[float] = None
+ self, rate: float, amount: float | None = None, open_rate: float | None = None
) -> float:
"""
Calculate the absolute profit in stake currency between Close and Open trade
@@ -1098,7 +1104,7 @@ class LocalTrade:
return prof.profit_abs
def calculate_profit(
- self, rate: float, amount: Optional[float] = None, open_rate: Optional[float] = None
+ self, rate: float, amount: float | None = None, open_rate: float | None = None
) -> ProfitStruct:
"""
Calculate profit metrics (absolute, ratio, total, total ratio).
@@ -1146,7 +1152,7 @@ class LocalTrade:
)
def calc_profit_ratio(
- self, rate: float, amount: Optional[float] = None, open_rate: Optional[float] = None
+ self, rate: float, amount: float | None = None, open_rate: float | None = None
) -> float:
"""
Calculates the profit as ratio (including fee).
@@ -1246,7 +1252,7 @@ class LocalTrade:
self.close_profit = (close_profit_abs / total_stake) * self.leverage
self.close_profit_abs = close_profit_abs
- def select_order_by_order_id(self, order_id: str) -> Optional[Order]:
+ def select_order_by_order_id(self, order_id: str) -> Order | None:
"""
Finds order object by Order id.
:param order_id: Exchange order id
@@ -1258,10 +1264,10 @@ class LocalTrade:
def select_order(
self,
- order_side: Optional[str] = None,
- is_open: Optional[bool] = None,
+ order_side: str | None = None,
+ is_open: bool | None = None,
only_filled: bool = False,
- ) -> Optional[Order]:
+ ) -> Order | None:
"""
Finds latest order for this orderside and status
:param order_side: ft_order_side of the order (either 'buy', 'sell' or 'stoploss')
@@ -1281,7 +1287,7 @@ class LocalTrade:
else:
return None
- def select_filled_orders(self, order_side: Optional[str] = None) -> list["Order"]:
+ def select_filled_orders(self, order_side: str | None = None) -> list["Order"]:
"""
Finds filled orders for this order side.
Will not return open orders which already partially filled.
@@ -1332,7 +1338,7 @@ class LocalTrade:
return data[0].value
return default
- def get_custom_data_entry(self, key: str) -> Optional[_CustomData]:
+ def get_custom_data_entry(self, key: str) -> _CustomData | None:
"""
Get custom data for this trade
:param key: key of the custom data
@@ -1385,7 +1391,7 @@ class LocalTrade:
return len(self.select_filled_orders("sell"))
@property
- def sell_reason(self) -> Optional[str]:
+ def sell_reason(self) -> str | None:
"""DEPRECATED! Please use exit_reason instead."""
return self.exit_reason
@@ -1396,10 +1402,10 @@ class LocalTrade:
@staticmethod
def get_trades_proxy(
*,
- pair: Optional[str] = None,
- is_open: Optional[bool] = None,
- open_date: Optional[datetime] = None,
- close_date: Optional[datetime] = None,
+ pair: str | None = None,
+ is_open: bool | None = None,
+ open_date: datetime | None = None,
+ close_date: datetime | None = None,
) -> list["LocalTrade"]:
"""
Helper function to query Trades.
@@ -1442,6 +1448,11 @@ class LocalTrade:
LocalTrade.bt_trades_open.remove(trade)
LocalTrade.bt_trades_open_pp[trade.pair].remove(trade)
LocalTrade.bt_open_open_trade_count -= 1
+ if (trade.close_date_utc - trade.open_date_utc) > timedelta(minutes=trade.timeframe):
+ # Only subtract trades that are open for more than 1 candle
+ # To avoid exceeding max_open_trades.
+ # Must be reset at the start of every candle during backesting.
+ LocalTrade.bt_open_open_trade_count_candle -= 1
LocalTrade.bt_trades.append(trade)
LocalTrade.bt_total_profit += trade.close_profit_abs
@@ -1451,6 +1462,7 @@ class LocalTrade:
LocalTrade.bt_trades_open.append(trade)
LocalTrade.bt_trades_open_pp[trade.pair].append(trade)
LocalTrade.bt_open_open_trade_count += 1
+ LocalTrade.bt_open_open_trade_count_candle += 1
else:
LocalTrade.bt_trades.append(trade)
@@ -1459,6 +1471,9 @@ class LocalTrade:
LocalTrade.bt_trades_open.remove(trade)
LocalTrade.bt_trades_open_pp[trade.pair].remove(trade)
LocalTrade.bt_open_open_trade_count -= 1
+ # TODO: The below may have odd behavior in case of canceled entries
+ # It might need to be removed so the trade "counts" as open for this candle.
+ LocalTrade.bt_open_open_trade_count_candle -= 1
@staticmethod
def get_open_trades() -> list[Any]:
@@ -1619,92 +1634,92 @@ class Trade(ModelBase, LocalTrade):
exchange: Mapped[str] = mapped_column(String(25), nullable=False) # type: ignore
pair: Mapped[str] = mapped_column(String(25), nullable=False, index=True) # type: ignore
- base_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) # type: ignore
- stake_currency: Mapped[Optional[str]] = mapped_column(String(25), nullable=True) # type: ignore
+ base_currency: Mapped[str | None] = mapped_column(String(25), nullable=True) # type: ignore
+ stake_currency: Mapped[str | None] = mapped_column(String(25), nullable=True) # type: ignore
is_open: Mapped[bool] = mapped_column(nullable=False, default=True, index=True) # type: ignore
fee_open: Mapped[float] = mapped_column(Float(), nullable=False, default=0.0) # type: ignore
- fee_open_cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore
- fee_open_currency: Mapped[Optional[str]] = mapped_column( # type: ignore
+ fee_open_cost: Mapped[float | None] = mapped_column(Float(), nullable=True) # type: ignore
+ fee_open_currency: Mapped[str | None] = mapped_column( # type: ignore
String(25), nullable=True
)
- fee_close: Mapped[Optional[float]] = mapped_column( # type: ignore
+ fee_close: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=False, default=0.0
)
- fee_close_cost: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore
- fee_close_currency: Mapped[Optional[str]] = mapped_column( # type: ignore
+ fee_close_cost: Mapped[float | None] = mapped_column(Float(), nullable=True) # type: ignore
+ fee_close_currency: Mapped[str | None] = mapped_column( # type: ignore
String(25), nullable=True
)
open_rate: Mapped[float] = mapped_column(Float()) # type: ignore
- open_rate_requested: Mapped[Optional[float]] = mapped_column( # type: ignore
+ open_rate_requested: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True
)
# open_trade_value - calculated via _calc_open_trade_value
open_trade_value: Mapped[float] = mapped_column(Float(), nullable=True) # type: ignore
- close_rate: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore
- close_rate_requested: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore
+ close_rate: Mapped[float | None] = mapped_column(Float()) # type: ignore
+ close_rate_requested: Mapped[float | None] = mapped_column(Float()) # type: ignore
realized_profit: Mapped[float] = mapped_column( # type: ignore
Float(), default=0.0, nullable=True
)
- close_profit: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore
- close_profit_abs: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore
+ close_profit: Mapped[float | None] = mapped_column(Float()) # type: ignore
+ close_profit_abs: Mapped[float | None] = mapped_column(Float()) # type: ignore
stake_amount: Mapped[float] = mapped_column(Float(), nullable=False) # type: ignore
- max_stake_amount: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore
+ max_stake_amount: Mapped[float | None] = mapped_column(Float()) # type: ignore
amount: Mapped[float] = mapped_column(Float()) # type: ignore
- amount_requested: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore
+ amount_requested: Mapped[float | None] = mapped_column(Float()) # type: ignore
open_date: Mapped[datetime] = mapped_column( # type: ignore
nullable=False, default=datetime.now
)
- close_date: Mapped[Optional[datetime]] = mapped_column() # type: ignore
+ close_date: Mapped[datetime | None] = mapped_column() # type: ignore
# absolute value of the stop loss
stop_loss: Mapped[float] = mapped_column(Float(), nullable=True, default=0.0) # type: ignore
# percentage value of the stop loss
- stop_loss_pct: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore
+ stop_loss_pct: Mapped[float | None] = mapped_column(Float(), nullable=True) # type: ignore
# absolute value of the initial stop loss
- initial_stop_loss: Mapped[Optional[float]] = mapped_column( # type: ignore
+ initial_stop_loss: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True, default=0.0
)
# percentage value of the initial stop loss
- initial_stop_loss_pct: Mapped[Optional[float]] = mapped_column( # type: ignore
+ initial_stop_loss_pct: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True
)
is_stop_loss_trailing: Mapped[bool] = mapped_column( # type: ignore
nullable=False, default=False
)
# absolute value of the highest reached price
- max_rate: Mapped[Optional[float]] = mapped_column( # type: ignore
+ max_rate: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True, default=0.0
)
# Lowest price reached
- min_rate: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore
- exit_reason: Mapped[Optional[str]] = mapped_column( # type: ignore
+ min_rate: Mapped[float | None] = mapped_column(Float(), nullable=True) # type: ignore
+ exit_reason: Mapped[str | None] = mapped_column( # type: ignore
String(CUSTOM_TAG_MAX_LENGTH), nullable=True
)
- exit_order_status: Mapped[Optional[str]] = mapped_column( # type: ignore
+ exit_order_status: Mapped[str | None] = mapped_column( # type: ignore
String(100), nullable=True
)
- strategy: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) # type: ignore
- enter_tag: Mapped[Optional[str]] = mapped_column( # type: ignore
+ strategy: Mapped[str | None] = mapped_column(String(100), nullable=True) # type: ignore
+ enter_tag: Mapped[str | None] = mapped_column( # type: ignore
String(CUSTOM_TAG_MAX_LENGTH), nullable=True
)
- timeframe: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore
+ timeframe: Mapped[int | None] = mapped_column(Integer, nullable=True) # type: ignore
trading_mode: Mapped[TradingMode] = mapped_column( # type: ignore
Enum(TradingMode), nullable=True
)
- amount_precision: Mapped[Optional[float]] = mapped_column( # type: ignore
+ amount_precision: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True
)
- price_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore
- precision_mode: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore
- precision_mode_price: Mapped[Optional[int]] = mapped_column( # type: ignore
+ price_precision: Mapped[float | None] = mapped_column(Float(), nullable=True) # type: ignore
+ precision_mode: Mapped[int | None] = mapped_column(Integer, nullable=True) # type: ignore
+ precision_mode_price: Mapped[int | None] = mapped_column( # type: ignore
Integer, nullable=True
)
- contract_size: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore
+ contract_size: Mapped[float | None] = mapped_column(Float(), nullable=True) # type: ignore
# Leverage trading properties
leverage: Mapped[float] = mapped_column(Float(), nullable=True, default=1.0) # type: ignore
is_short: Mapped[bool] = mapped_column(nullable=False, default=False) # type: ignore
- liquidation_price: Mapped[Optional[float]] = mapped_column( # type: ignore
+ liquidation_price: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True
)
@@ -1714,10 +1729,10 @@ class Trade(ModelBase, LocalTrade):
)
# Futures properties
- funding_fees: Mapped[Optional[float]] = mapped_column( # type: ignore
+ funding_fees: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True, default=None
)
- funding_fee_running: Mapped[Optional[float]] = mapped_column( # type: ignore
+ funding_fee_running: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True, default=None
)
@@ -1756,10 +1771,10 @@ class Trade(ModelBase, LocalTrade):
@staticmethod
def get_trades_proxy(
*,
- pair: Optional[str] = None,
- is_open: Optional[bool] = None,
- open_date: Optional[datetime] = None,
- close_date: Optional[datetime] = None,
+ pair: str | None = None,
+ is_open: bool | None = None,
+ open_date: datetime | None = None,
+ close_date: datetime | None = None,
) -> list["LocalTrade"]:
"""
Helper function to query Trades.j
@@ -1922,7 +1937,7 @@ class Trade(ModelBase, LocalTrade):
]
@staticmethod
- def get_enter_tag_performance(pair: Optional[str]) -> list[dict[str, Any]]:
+ def get_enter_tag_performance(pair: str | None) -> list[dict[str, Any]]:
"""
Returns List of dicts containing all Trades, based on buy tag performance
Can either be average for all pairs or a specific pair provided
@@ -1957,7 +1972,7 @@ class Trade(ModelBase, LocalTrade):
]
@staticmethod
- def get_exit_reason_performance(pair: Optional[str]) -> list[dict[str, Any]]:
+ def get_exit_reason_performance(pair: str | None) -> list[dict[str, Any]]:
"""
Returns List of dicts containing all Trades, based on exit reason performance
Can either be average for all pairs or a specific pair provided
@@ -1991,7 +2006,7 @@ class Trade(ModelBase, LocalTrade):
]
@staticmethod
- def get_mix_tag_performance(pair: Optional[str]) -> list[dict[str, Any]]:
+ def get_mix_tag_performance(pair: str | None) -> list[dict[str, Any]]:
"""
Returns List of dicts containing all Trades, based on entry_tag + exit_reason performance
Can either be average for all pairs or a specific pair provided
@@ -2048,7 +2063,7 @@ class Trade(ModelBase, LocalTrade):
return resp
@staticmethod
- def get_best_pair(start_date: Optional[datetime] = None):
+ def get_best_pair(start_date: datetime | None = None):
"""
Get best pair with closed trade.
NOTE: Not supported in Backtesting.
@@ -2068,7 +2083,7 @@ class Trade(ModelBase, LocalTrade):
return best_pair
@staticmethod
- def get_trading_volume(start_date: Optional[datetime] = None) -> float:
+ def get_trading_volume(start_date: datetime | None = None) -> float:
"""
Get Trade volume based on Orders
NOTE: Not supported in Backtesting.
diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py
index 8ce851476..cf86f070d 100644
--- a/freqtrade/plot/plotting.py
+++ b/freqtrade/plot/plotting.py
@@ -1,7 +1,6 @@
import logging
from datetime import datetime, timezone
from pathlib import Path
-from typing import Optional
import pandas as pd
@@ -406,7 +405,7 @@ def add_areas(fig, row: int, data: pd.DataFrame, indicators) -> make_subplots:
return fig
-def create_scatter(data, column_name, color, direction) -> Optional[go.Scatter]:
+def create_scatter(data, column_name, color, direction) -> go.Scatter | None:
if column_name in data.columns:
df_short = data[data[column_name] == 1]
if len(df_short) > 0:
@@ -432,11 +431,11 @@ def create_scatter(data, column_name, color, direction) -> Optional[go.Scatter]:
def generate_candlestick_graph(
pair: str,
data: pd.DataFrame,
- trades: Optional[pd.DataFrame] = None,
+ trades: pd.DataFrame | None = None,
*,
- indicators1: Optional[list[str]] = None,
- indicators2: Optional[list[str]] = None,
- plot_config: Optional[dict[str, dict]] = None,
+ indicators1: list[str] | None = None,
+ indicators2: list[str] | None = None,
+ plot_config: dict[str, dict] | None = None,
) -> go.Figure:
"""
Generate the graph from the data generated by Backtesting or from DB
diff --git a/freqtrade/plugins/pairlist/AgeFilter.py b/freqtrade/plugins/pairlist/AgeFilter.py
index cc89e3421..03ba12770 100644
--- a/freqtrade/plugins/pairlist/AgeFilter.py
+++ b/freqtrade/plugins/pairlist/AgeFilter.py
@@ -5,7 +5,6 @@ Minimum age (days listed) pair list filter
import logging
from copy import deepcopy
from datetime import timedelta
-from typing import Optional
from pandas import DataFrame
@@ -126,7 +125,7 @@ class AgeFilter(IPairList):
self.log_once(f"Validated {len(pairlist)} pairs.", logger.info)
return pairlist
- def _validate_pair_loc(self, pair: str, daily_candles: Optional[DataFrame]) -> bool:
+ def _validate_pair_loc(self, pair: str, daily_candles: DataFrame | None) -> bool:
"""
Validate age for the ticker
:param pair: Pair that's currently validated
diff --git a/freqtrade/plugins/pairlist/IPairList.py b/freqtrade/plugins/pairlist/IPairList.py
index a1ce4dc36..89a9e68cd 100644
--- a/freqtrade/plugins/pairlist/IPairList.py
+++ b/freqtrade/plugins/pairlist/IPairList.py
@@ -6,7 +6,7 @@ import logging
from abc import ABC, abstractmethod
from copy import deepcopy
from enum import Enum
-from typing import Any, Literal, Optional, TypedDict, Union
+from typing import Any, Literal, TypedDict
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException
@@ -25,37 +25,37 @@ class __PairlistParameterBase(TypedDict):
class __NumberPairlistParameter(__PairlistParameterBase):
type: Literal["number"]
- default: Union[int, float, None]
+ default: int | float | None
class __StringPairlistParameter(__PairlistParameterBase):
type: Literal["string"]
- default: Union[str, None]
+ default: str | None
class __OptionPairlistParameter(__PairlistParameterBase):
type: Literal["option"]
- default: Union[str, None]
+ default: str | None
options: list[str]
class __ListPairListParamenter(__PairlistParameterBase):
type: Literal["list"]
- default: Union[list[str], None]
+ default: list[str] | None
class __BoolPairlistParameter(__PairlistParameterBase):
type: Literal["boolean"]
- default: Union[bool, None]
+ default: bool | None
-PairlistParameter = Union[
- __NumberPairlistParameter,
- __StringPairlistParameter,
- __OptionPairlistParameter,
- __BoolPairlistParameter,
- __ListPairListParamenter,
-]
+PairlistParameter = (
+ __NumberPairlistParameter
+ | __StringPairlistParameter
+ | __OptionPairlistParameter
+ | __BoolPairlistParameter
+ | __ListPairListParamenter
+)
class SupportsBacktesting(str, Enum):
@@ -153,7 +153,7 @@ class IPairList(LoggingMixin, ABC):
-> Please overwrite in subclasses
"""
- def _validate_pair(self, pair: str, ticker: Optional[Ticker]) -> bool:
+ def _validate_pair(self, pair: str, ticker: Ticker | None) -> bool:
"""
Check one pair against Pairlist Handler's specific conditions.
diff --git a/freqtrade/plugins/pairlist/MarketCapPairList.py b/freqtrade/plugins/pairlist/MarketCapPairList.py
index 2394e910e..b95cec50f 100644
--- a/freqtrade/plugins/pairlist/MarketCapPairList.py
+++ b/freqtrade/plugins/pairlist/MarketCapPairList.py
@@ -5,6 +5,7 @@ Provides dynamic pair list based on Market Cap
"""
import logging
+import math
from cachetools import TTLCache
@@ -57,7 +58,11 @@ class MarketCapPairList(IPairList):
)
if self._max_rank > 250:
- raise OperationalException("This filter only support marketcap rank up to 250.")
+ self.logger.warning(
+ f"The max rank you have set ({self._max_rank}) is quite high. "
+ "This may lead to coingecko API rate limit issues. "
+ "Please ensure this value is necessary for your use case.",
+ )
@property
def needstickers(self) -> bool:
@@ -165,7 +170,11 @@ class MarketCapPairList(IPairList):
data = []
if not self._categories:
- data = self._coingecko.get_coins_markets(**default_kwargs)
+ pages_required = math.ceil(self._max_rank / 250)
+ for page in range(1, pages_required + 1):
+ default_kwargs["page"] = str(page)
+ page_data = self._coingecko.get_coins_markets(**default_kwargs)
+ data.extend(page_data)
else:
for category in self._categories:
category_data = self._coingecko.get_coins_markets(
diff --git a/freqtrade/plugins/pairlist/PercentChangePairList.py b/freqtrade/plugins/pairlist/PercentChangePairList.py
index b428da75a..3062aba05 100644
--- a/freqtrade/plugins/pairlist/PercentChangePairList.py
+++ b/freqtrade/plugins/pairlist/PercentChangePairList.py
@@ -8,7 +8,7 @@ defined period or as coming from ticker
import logging
from datetime import timedelta
-from typing import Any, Optional
+from typing import Any
from cachetools import TTLCache
from pandas import DataFrame
@@ -46,7 +46,7 @@ class PercentChangePairList(IPairList):
self._lookback_days = self._pairlistconfig.get("lookback_days", 0)
self._lookback_timeframe = self._pairlistconfig.get("lookback_timeframe", "1d")
self._lookback_period = self._pairlistconfig.get("lookback_period", 0)
- self._sort_direction: Optional[str] = self._pairlistconfig.get("sort_direction", "desc")
+ self._sort_direction: str | None = self._pairlistconfig.get("sort_direction", "desc")
self._def_candletype = self._config["candle_type_def"]
if (self._lookback_days > 0) & (self._lookback_period > 0):
@@ -311,7 +311,7 @@ class PercentChangePairList(IPairList):
else:
filtered_tickers[i]["percentage"] = tickers[p["symbol"]]["percentage"]
- def _validate_pair(self, pair: str, ticker: Optional[Ticker]) -> bool:
+ def _validate_pair(self, pair: str, ticker: Ticker | None) -> bool:
"""
Check if one price-step (pip) is > than a certain barrier.
:param pair: Pair that's currently validated
diff --git a/freqtrade/plugins/pairlist/PrecisionFilter.py b/freqtrade/plugins/pairlist/PrecisionFilter.py
index 43072a26a..d0af200cd 100644
--- a/freqtrade/plugins/pairlist/PrecisionFilter.py
+++ b/freqtrade/plugins/pairlist/PrecisionFilter.py
@@ -3,7 +3,6 @@ Precision pair list filter
"""
import logging
-from typing import Optional
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import ROUND_UP
@@ -50,7 +49,7 @@ class PrecisionFilter(IPairList):
def description() -> str:
return "Filters low-value coins which would not allow setting stoplosses."
- def _validate_pair(self, pair: str, ticker: Optional[Ticker]) -> bool:
+ def _validate_pair(self, pair: str, ticker: Ticker | None) -> bool:
"""
Check if pair has enough room to add a stoploss to avoid "unsellable" buys of very
low value pairs.
diff --git a/freqtrade/plugins/pairlist/PriceFilter.py b/freqtrade/plugins/pairlist/PriceFilter.py
index f963b1247..ea5ef27a2 100644
--- a/freqtrade/plugins/pairlist/PriceFilter.py
+++ b/freqtrade/plugins/pairlist/PriceFilter.py
@@ -3,7 +3,6 @@ Price pair list filter
"""
import logging
-from typing import Optional
from freqtrade.exceptions import OperationalException
from freqtrade.exchange.exchange_types import Ticker
@@ -101,7 +100,7 @@ class PriceFilter(IPairList):
},
}
- def _validate_pair(self, pair: str, ticker: Optional[Ticker]) -> bool:
+ def _validate_pair(self, pair: str, ticker: Ticker | None) -> bool:
"""
Check if one price-step (pip) is > than a certain barrier.
:param pair: Pair that's currently validated
diff --git a/freqtrade/plugins/pairlist/ProducerPairList.py b/freqtrade/plugins/pairlist/ProducerPairList.py
index e286b3560..89e4a0869 100644
--- a/freqtrade/plugins/pairlist/ProducerPairList.py
+++ b/freqtrade/plugins/pairlist/ProducerPairList.py
@@ -5,7 +5,6 @@ Provides pair list from Leader data
"""
import logging
-from typing import Optional
from freqtrade.exceptions import OperationalException
from freqtrade.exchange.exchange_types import Tickers
@@ -83,7 +82,7 @@ class ProducerPairList(IPairList):
},
}
- def _filter_pairlist(self, pairlist: Optional[list[str]]):
+ def _filter_pairlist(self, pairlist: list[str] | None):
upstream_pairlist = self._pairlistmanager._dataprovider.get_producer_pairs(
self._producer_name
)
diff --git a/freqtrade/plugins/pairlist/SpreadFilter.py b/freqtrade/plugins/pairlist/SpreadFilter.py
index 0ecd1909d..51e8e802b 100644
--- a/freqtrade/plugins/pairlist/SpreadFilter.py
+++ b/freqtrade/plugins/pairlist/SpreadFilter.py
@@ -3,7 +3,6 @@ Spread pair list filter
"""
import logging
-from typing import Optional
from freqtrade.exceptions import OperationalException
from freqtrade.exchange.exchange_types import Ticker
@@ -61,7 +60,7 @@ class SpreadFilter(IPairList):
},
}
- def _validate_pair(self, pair: str, ticker: Optional[Ticker]) -> bool:
+ def _validate_pair(self, pair: str, ticker: Ticker | None) -> bool:
"""
Validate spread for the ticker
:param pair: Pair that's currently validated
diff --git a/freqtrade/plugins/pairlist/VolatilityFilter.py b/freqtrade/plugins/pairlist/VolatilityFilter.py
index 76e93a1f3..87ca488c1 100644
--- a/freqtrade/plugins/pairlist/VolatilityFilter.py
+++ b/freqtrade/plugins/pairlist/VolatilityFilter.py
@@ -5,7 +5,6 @@ Volatility pairlist filter
import logging
import sys
from datetime import timedelta
-from typing import Optional
import numpy as np
from cachetools import TTLCache
@@ -37,7 +36,7 @@ class VolatilityFilter(IPairList):
self._max_volatility = self._pairlistconfig.get("max_volatility", sys.maxsize)
self._refresh_period = self._pairlistconfig.get("refresh_period", 1440)
self._def_candletype = self._config["candle_type_def"]
- self._sort_direction: Optional[str] = self._pairlistconfig.get("sort_direction", None)
+ self._sort_direction: str | None = self._pairlistconfig.get("sort_direction", None)
self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period)
@@ -147,7 +146,7 @@ class VolatilityFilter(IPairList):
)
return resulting_pairlist
- def _calculate_volatility(self, pair: str, daily_candles: DataFrame) -> Optional[float]:
+ def _calculate_volatility(self, pair: str, daily_candles: DataFrame) -> float | None:
# Check symbol in cache
if (volatility_avg := self._pair_cache.get(pair, None)) is not None:
return volatility_avg
diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py
index cff958865..ca5543bb0 100644
--- a/freqtrade/plugins/pairlist/rangestabilityfilter.py
+++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py
@@ -4,7 +4,6 @@ Rate of change pairlist filter
import logging
from datetime import timedelta
-from typing import Optional
from cachetools import TTLCache
from pandas import DataFrame
@@ -31,7 +30,7 @@ class RangeStabilityFilter(IPairList):
self._max_rate_of_change = self._pairlistconfig.get("max_rate_of_change")
self._refresh_period = self._pairlistconfig.get("refresh_period", 86400)
self._def_candletype = self._config["candle_type_def"]
- self._sort_direction: Optional[str] = self._pairlistconfig.get("sort_direction", None)
+ self._sort_direction: str | None = self._pairlistconfig.get("sort_direction", None)
self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period)
@@ -143,7 +142,7 @@ class RangeStabilityFilter(IPairList):
)
return resulting_pairlist
- def _calculate_rate_of_change(self, pair: str, daily_candles: DataFrame) -> Optional[float]:
+ def _calculate_rate_of_change(self, pair: str, daily_candles: DataFrame) -> float | None:
# Check symbol in cache
if (pct_change := self._pair_cache.get(pair, None)) is not None:
return pct_change
diff --git a/freqtrade/plugins/pairlistmanager.py b/freqtrade/plugins/pairlistmanager.py
index 2171629d3..73786cba6 100644
--- a/freqtrade/plugins/pairlistmanager.py
+++ b/freqtrade/plugins/pairlistmanager.py
@@ -4,7 +4,6 @@ PairList manager class
import logging
from functools import partial
-from typing import Optional
from cachetools import TTLCache, cached
@@ -24,16 +23,14 @@ logger = logging.getLogger(__name__)
class PairListManager(LoggingMixin):
- def __init__(
- self, exchange, config: Config, dataprovider: Optional[DataProvider] = None
- ) -> None:
+ def __init__(self, exchange, config: Config, dataprovider: DataProvider | None = None) -> None:
self._exchange = exchange
self._config = config
self._whitelist = self._config["exchange"].get("pair_whitelist")
self._blacklist = self._config["exchange"].get("pair_blacklist", [])
self._pairlist_handlers: list[IPairList] = []
self._tickers_needed = False
- self._dataprovider: Optional[DataProvider] = dataprovider
+ self._dataprovider: DataProvider | None = dataprovider
for pairlist_handler_config in self._config.get("pairlists", []):
pairlist_handler = PairListResolver.load_pairlist(
pairlist_handler_config["method"],
@@ -193,7 +190,7 @@ class PairListManager(LoggingMixin):
return whitelist
def create_pair_list(
- self, pairs: list[str], timeframe: Optional[str] = None
+ self, pairs: list[str], timeframe: str | None = None
) -> ListPairsWithTimeframes:
"""
Create list of pair tuples with (pair, timeframe)
diff --git a/freqtrade/plugins/protectionmanager.py b/freqtrade/plugins/protectionmanager.py
index 648cd6bbb..187b9ee63 100644
--- a/freqtrade/plugins/protectionmanager.py
+++ b/freqtrade/plugins/protectionmanager.py
@@ -4,7 +4,7 @@ Protection manager class
import logging
from datetime import datetime, timezone
-from typing import Any, Optional
+from typing import Any
from freqtrade.constants import Config, LongShort
from freqtrade.exceptions import ConfigurationError
@@ -47,9 +47,7 @@ class ProtectionManager:
"""
return [{p.name: p.short_desc()} for p in self._protection_handlers]
- def global_stop(
- self, now: Optional[datetime] = None, side: LongShort = "long"
- ) -> Optional[PairLock]:
+ def global_stop(self, now: datetime | None = None, side: LongShort = "long") -> PairLock | None:
if not now:
now = datetime.now(timezone.utc)
result = None
@@ -64,8 +62,8 @@ class ProtectionManager:
return result
def stop_per_pair(
- self, pair, now: Optional[datetime] = None, side: LongShort = "long"
- ) -> Optional[PairLock]:
+ self, pair, now: datetime | None = None, side: LongShort = "long"
+ ) -> PairLock | None:
if not now:
now = datetime.now(timezone.utc)
result = None
diff --git a/freqtrade/plugins/protections/cooldown_period.py b/freqtrade/plugins/protections/cooldown_period.py
index d30bd87e5..0f6aaa79a 100644
--- a/freqtrade/plugins/protections/cooldown_period.py
+++ b/freqtrade/plugins/protections/cooldown_period.py
@@ -1,6 +1,5 @@
import logging
from datetime import datetime, timedelta
-from typing import Optional
from freqtrade.constants import LongShort
from freqtrade.persistence import Trade
@@ -26,7 +25,7 @@ class CooldownPeriod(IProtection):
"""
return f"{self.name} - Cooldown period {self.unlock_reason_time_element}."
- def _cooldown_period(self, pair: str, date_now: datetime) -> Optional[ProtectionReturn]:
+ def _cooldown_period(self, pair: str, date_now: datetime) -> ProtectionReturn | None:
"""
Get last trade for this pair
"""
@@ -53,7 +52,7 @@ class CooldownPeriod(IProtection):
return None
- def global_stop(self, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]:
+ def global_stop(self, date_now: datetime, side: LongShort) -> ProtectionReturn | None:
"""
Stops trading (position entering) for all pairs
This must evaluate to true for the whole period of the "cooldown period".
@@ -65,7 +64,7 @@ class CooldownPeriod(IProtection):
def stop_per_pair(
self, pair: str, date_now: datetime, side: LongShort
- ) -> Optional[ProtectionReturn]:
+ ) -> ProtectionReturn | None:
"""
Stops trading (position entering) for this pair
This must evaluate to true for the whole period of the "cooldown period".
diff --git a/freqtrade/plugins/protections/iprotection.py b/freqtrade/plugins/protections/iprotection.py
index a2264675e..38a726dcc 100644
--- a/freqtrade/plugins/protections/iprotection.py
+++ b/freqtrade/plugins/protections/iprotection.py
@@ -2,7 +2,7 @@ import logging
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
-from typing import Any, Optional
+from typing import Any
from freqtrade.constants import Config, LongShort
from freqtrade.exchange import timeframe_to_minutes
@@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
class ProtectionReturn:
lock: bool
until: datetime
- reason: Optional[str]
+ reason: str | None
lock_side: str = "*"
@@ -31,10 +31,10 @@ class IProtection(LoggingMixin, ABC):
def __init__(self, config: Config, protection_config: dict[str, Any]) -> None:
self._config = config
self._protection_config = protection_config
- self._stop_duration_candles: Optional[int] = None
+ self._stop_duration_candles: int | None = None
self._stop_duration: int = 0
- self._lookback_period_candles: Optional[int] = None
- self._unlock_at: Optional[str] = None
+ self._lookback_period_candles: int | None = None
+ self._unlock_at: str | None = None
tf_in_min = timeframe_to_minutes(config["timeframe"])
if "stop_duration_candles" in protection_config:
@@ -102,7 +102,7 @@ class IProtection(LoggingMixin, ABC):
"""
@abstractmethod
- def global_stop(self, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]:
+ def global_stop(self, date_now: datetime, side: LongShort) -> ProtectionReturn | None:
"""
Stops trading (position entering) for all pairs
This must evaluate to true for the whole period of the "cooldown period".
@@ -111,7 +111,7 @@ class IProtection(LoggingMixin, ABC):
@abstractmethod
def stop_per_pair(
self, pair: str, date_now: datetime, side: LongShort
- ) -> Optional[ProtectionReturn]:
+ ) -> ProtectionReturn | None:
"""
Stops trading (position entering) for this pair
This must evaluate to true for the whole period of the "cooldown period".
diff --git a/freqtrade/plugins/protections/low_profit_pairs.py b/freqtrade/plugins/protections/low_profit_pairs.py
index e6ee0db64..84934f394 100644
--- a/freqtrade/plugins/protections/low_profit_pairs.py
+++ b/freqtrade/plugins/protections/low_profit_pairs.py
@@ -1,6 +1,6 @@
import logging
from datetime import datetime, timedelta
-from typing import Any, Optional
+from typing import Any
from freqtrade.constants import Config, LongShort
from freqtrade.persistence import Trade
@@ -41,7 +41,7 @@ class LowProfitPairs(IProtection):
def _low_profit(
self, date_now: datetime, pair: str, side: LongShort
- ) -> Optional[ProtectionReturn]:
+ ) -> ProtectionReturn | None:
"""
Evaluate recent trades for pair
"""
@@ -81,7 +81,7 @@ class LowProfitPairs(IProtection):
return None
- def global_stop(self, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]:
+ def global_stop(self, date_now: datetime, side: LongShort) -> ProtectionReturn | None:
"""
Stops trading (position entering) for all pairs
This must evaluate to true for the whole period of the "cooldown period".
@@ -92,7 +92,7 @@ class LowProfitPairs(IProtection):
def stop_per_pair(
self, pair: str, date_now: datetime, side: LongShort
- ) -> Optional[ProtectionReturn]:
+ ) -> ProtectionReturn | None:
"""
Stops trading (position entering) for this pair
This must evaluate to true for the whole period of the "cooldown period".
diff --git a/freqtrade/plugins/protections/max_drawdown_protection.py b/freqtrade/plugins/protections/max_drawdown_protection.py
index 903f9fbbf..e8996d3a8 100644
--- a/freqtrade/plugins/protections/max_drawdown_protection.py
+++ b/freqtrade/plugins/protections/max_drawdown_protection.py
@@ -1,6 +1,6 @@
import logging
from datetime import datetime, timedelta
-from typing import Any, Optional
+from typing import Any
import pandas as pd
@@ -42,7 +42,7 @@ class MaxDrawdown(IProtection):
f"locking {self.unlock_reason_time_element}."
)
- def _max_drawdown(self, date_now: datetime) -> Optional[ProtectionReturn]:
+ def _max_drawdown(self, date_now: datetime) -> ProtectionReturn | None:
"""
Evaluate recent trades for drawdown ...
"""
@@ -81,7 +81,7 @@ class MaxDrawdown(IProtection):
return None
- def global_stop(self, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]:
+ def global_stop(self, date_now: datetime, side: LongShort) -> ProtectionReturn | None:
"""
Stops trading (position entering) for all pairs
This must evaluate to true for the whole period of the "cooldown period".
@@ -92,7 +92,7 @@ class MaxDrawdown(IProtection):
def stop_per_pair(
self, pair: str, date_now: datetime, side: LongShort
- ) -> Optional[ProtectionReturn]:
+ ) -> ProtectionReturn | None:
"""
Stops trading (position entering) for this pair
This must evaluate to true for the whole period of the "cooldown period".
diff --git a/freqtrade/plugins/protections/stoploss_guard.py b/freqtrade/plugins/protections/stoploss_guard.py
index 47ebc3696..a429a2f80 100644
--- a/freqtrade/plugins/protections/stoploss_guard.py
+++ b/freqtrade/plugins/protections/stoploss_guard.py
@@ -1,6 +1,6 @@
import logging
from datetime import datetime, timedelta
-from typing import Any, Optional
+from typing import Any
from freqtrade.constants import Config, LongShort
from freqtrade.enums import ExitType
@@ -42,8 +42,8 @@ class StoplossGuard(IProtection):
)
def _stoploss_guard(
- self, date_now: datetime, pair: Optional[str], side: LongShort
- ) -> Optional[ProtectionReturn]:
+ self, date_now: datetime, pair: str | None, side: LongShort
+ ) -> ProtectionReturn | None:
"""
Evaluate recent trades
"""
@@ -86,7 +86,7 @@ class StoplossGuard(IProtection):
lock_side=(side if self._only_per_side else "*"),
)
- def global_stop(self, date_now: datetime, side: LongShort) -> Optional[ProtectionReturn]:
+ def global_stop(self, date_now: datetime, side: LongShort) -> ProtectionReturn | None:
"""
Stops trading (position entering) for all pairs
This must evaluate to true for the whole period of the "cooldown period".
@@ -99,7 +99,7 @@ class StoplossGuard(IProtection):
def stop_per_pair(
self, pair: str, date_now: datetime, side: LongShort
- ) -> Optional[ProtectionReturn]:
+ ) -> ProtectionReturn | None:
"""
Stops trading (position entering) for this pair
This must evaluate to true for the whole period of the "cooldown period".
diff --git a/freqtrade/resolvers/exchange_resolver.py b/freqtrade/resolvers/exchange_resolver.py
index 9d5a0d557..835c3c0af 100644
--- a/freqtrade/resolvers/exchange_resolver.py
+++ b/freqtrade/resolvers/exchange_resolver.py
@@ -4,7 +4,7 @@ This module loads custom exchanges
import logging
from inspect import isclass
-from typing import Any, Optional
+from typing import Any
import freqtrade.exchange as exchanges
from freqtrade.constants import Config, ExchangeConfig
@@ -26,7 +26,7 @@ class ExchangeResolver(IResolver):
def load_exchange(
config: Config,
*,
- exchange_config: Optional[ExchangeConfig] = None,
+ exchange_config: ExchangeConfig | None = None,
validate: bool = True,
load_leverage_tiers: bool = False,
) -> Exchange:
diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py
index 9aa103a7e..cb0c3de33 100644
--- a/freqtrade/resolvers/iresolver.py
+++ b/freqtrade/resolvers/iresolver.py
@@ -10,7 +10,7 @@ import logging
import sys
from collections.abc import Iterator
from pathlib import Path
-from typing import Any, Optional, Union
+from typing import Any
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException
@@ -43,17 +43,17 @@ class IResolver:
# Childclasses need to override this
object_type: type[Any]
object_type_str: str
- user_subdir: Optional[str] = None
- initial_search_path: Optional[Path] = None
+ user_subdir: str | None = None
+ initial_search_path: Path | None = None
# Optional config setting containing a path (strategy_path, freqaimodel_path)
- extra_path: Optional[str] = None
+ extra_path: str | None = None
@classmethod
def build_search_paths(
cls,
config: Config,
- user_subdir: Optional[str] = None,
- extra_dirs: Optional[list[str]] = None,
+ user_subdir: str | None = None,
+ extra_dirs: list[str] | None = None,
) -> list[Path]:
abs_paths: list[Path] = []
if cls.initial_search_path:
@@ -74,7 +74,7 @@ class IResolver:
@classmethod
def _get_valid_object(
- cls, module_path: Path, object_name: Optional[str], enum_failed: bool = False
+ cls, module_path: Path, object_name: str | None, enum_failed: bool = False
) -> Iterator[Any]:
"""
Generator returning objects with matching object_type and object_name in the path given.
@@ -131,7 +131,7 @@ class IResolver:
@classmethod
def _search_object(
cls, directory: Path, *, object_name: str, add_source: bool = False
- ) -> Union[tuple[Any, Path], tuple[None, None]]:
+ ) -> tuple[Any, Path] | tuple[None, None]:
"""
Search for the objectname in the given directory
:param directory: relative or absolute directory path
@@ -161,7 +161,7 @@ class IResolver:
@classmethod
def _load_object(
cls, paths: list[Path], *, object_name: str, add_source: bool = False, kwargs: dict
- ) -> Optional[Any]:
+ ) -> Any | None:
"""
Try to load object from path list.
"""
@@ -184,7 +184,7 @@ class IResolver:
@classmethod
def load_object(
- cls, object_name: str, config: Config, *, kwargs: dict, extra_dir: Optional[str] = None
+ cls, object_name: str, config: Config, *, kwargs: dict, extra_dir: str | None = None
) -> Any:
"""
Search and loads the specified object as configured in the child class.
@@ -245,7 +245,7 @@ class IResolver:
directory: Path,
enum_failed: bool,
recursive: bool = False,
- basedir: Optional[Path] = None,
+ basedir: Path | None = None,
) -> list[dict[str, Any]]:
"""
Searches a directory for valid objects
diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py
index 5ac1cb6f9..67751b5ae 100644
--- a/freqtrade/resolvers/strategy_resolver.py
+++ b/freqtrade/resolvers/strategy_resolver.py
@@ -10,7 +10,7 @@ from base64 import urlsafe_b64decode
from inspect import getfullargspec
from os import walk
from pathlib import Path
-from typing import Any, Optional
+from typing import Any
from freqtrade.configuration.config_validation import validate_migrated_strategy_settings
from freqtrade.constants import REQUIRED_ORDERTIF, REQUIRED_ORDERTYPES, USERPATH_STRATEGIES, Config
@@ -35,7 +35,7 @@ class StrategyResolver(IResolver):
extra_path = "strategy_path"
@staticmethod
- def load_strategy(config: Optional[Config] = None) -> IStrategy:
+ def load_strategy(config: Config | None = None) -> IStrategy:
"""
Load the custom class from config parameter
:param config: configuration dictionary or None
@@ -246,7 +246,7 @@ class StrategyResolver(IResolver):
@staticmethod
def _load_strategy(
- strategy_name: str, config: Config, extra_dir: Optional[str] = None
+ strategy_name: str, config: Config, extra_dir: str | None = None
) -> IStrategy:
"""
Search and loads the specified strategy.
diff --git a/freqtrade/rpc/api_server/api_auth.py b/freqtrade/rpc/api_server/api_auth.py
index d9b04ab81..5f63c0aa5 100644
--- a/freqtrade/rpc/api_server/api_auth.py
+++ b/freqtrade/rpc/api_server/api_auth.py
@@ -1,7 +1,7 @@
import logging
import secrets
from datetime import datetime, timedelta, timezone
-from typing import Any, Union
+from typing import Any
import jwt
from fastapi import APIRouter, Depends, HTTPException, Query, WebSocket, status
@@ -55,7 +55,7 @@ def get_user_from_token(token, secret_key: str, token_type: str = "access") -> s
# https://github.com/tiangolo/fastapi/blob/master/fastapi/security/api_key.py
async def validate_ws_token(
ws: WebSocket,
- ws_token: Union[str, None] = Query(default=None, alias="token"),
+ ws_token: str | None = Query(default=None, alias="token"),
api_config: dict[str, Any] = Depends(get_api_config),
):
secret_ws_token = api_config.get("ws_token", None)
diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py
index 3c0fd70b7..db8654539 100644
--- a/freqtrade/rpc/api_server/api_schemas.py
+++ b/freqtrade/rpc/api_server/api_schemas.py
@@ -1,5 +1,5 @@
from datetime import date, datetime
-from typing import Any, Optional, Union
+from typing import Any
from pydantic import AwareDatetime, BaseModel, RootModel, SerializeAsAny
@@ -9,9 +9,9 @@ from freqtrade.ft_types import ValidExchangesType
class ExchangeModePayloadMixin(BaseModel):
- trading_mode: Optional[TradingMode] = None
- margin_mode: Optional[MarginMode] = None
- exchange: Optional[str] = None
+ trading_mode: TradingMode | None = None
+ margin_mode: MarginMode | None = None
+ exchange: str | None = None
class Ping(BaseModel):
@@ -43,12 +43,12 @@ class BackgroundTaskStatus(BaseModel):
job_category: str
status: str
running: bool
- progress: Optional[float] = None
- error: Optional[str] = None
+ progress: float | None = None
+ error: str | None = None
class BackgroundTaskResult(BaseModel):
- error: Optional[str] = None
+ error: str | None = None
status: str
@@ -61,9 +61,9 @@ class Balance(BaseModel):
free: float
balance: float
used: float
- bot_owned: Optional[float] = None
+ bot_owned: float | None = None
est_stake: float
- est_stake_bot: Optional[float] = None
+ est_stake_bot: float | None = None
stake: str
# Starting with 2.x
side: str
@@ -160,7 +160,7 @@ class Profit(BaseModel):
max_drawdown_start_timestamp: int
max_drawdown_end: str
max_drawdown_end_timestamp: int
- trading_volume: Optional[float] = None
+ trading_volume: float | None = None
bot_start_timestamp: int
bot_start_date: str
@@ -173,7 +173,7 @@ class SellReason(BaseModel):
class Stats(BaseModel):
exit_reasons: dict[str, SellReason]
- durations: dict[str, Optional[float]]
+ durations: dict[str, float | None]
class DailyWeeklyMonthlyRecord(BaseModel):
@@ -192,50 +192,50 @@ class DailyWeeklyMonthly(BaseModel):
class UnfilledTimeout(BaseModel):
- entry: Optional[int] = None
- exit: Optional[int] = None
- unit: Optional[str] = None
- exit_timeout_count: Optional[int] = None
+ entry: int | None = None
+ exit: int | None = None
+ unit: str | None = None
+ exit_timeout_count: int | None = None
class OrderTypes(BaseModel):
entry: OrderTypeValues
exit: OrderTypeValues
- emergency_exit: Optional[OrderTypeValues] = None
- force_exit: Optional[OrderTypeValues] = None
- force_entry: Optional[OrderTypeValues] = None
+ emergency_exit: OrderTypeValues | None = None
+ force_exit: OrderTypeValues | None = None
+ force_entry: OrderTypeValues | None = None
stoploss: OrderTypeValues
stoploss_on_exchange: bool
- stoploss_on_exchange_interval: Optional[int] = None
+ stoploss_on_exchange_interval: int | None = None
class ShowConfig(BaseModel):
version: str
- strategy_version: Optional[str] = None
+ strategy_version: str | None = None
api_version: float
dry_run: bool
trading_mode: str
short_allowed: bool
stake_currency: str
stake_amount: str
- available_capital: Optional[float] = None
+ available_capital: float | None = None
stake_currency_decimals: int
max_open_trades: IntOrInf
minimal_roi: dict[str, Any]
- stoploss: Optional[float] = None
+ stoploss: float | None = None
stoploss_on_exchange: bool
- trailing_stop: Optional[bool] = None
- trailing_stop_positive: Optional[float] = None
- trailing_stop_positive_offset: Optional[float] = None
- trailing_only_offset_is_reached: Optional[bool] = None
- unfilledtimeout: Optional[UnfilledTimeout] = None # Empty in webserver mode
- order_types: Optional[OrderTypes] = None
- use_custom_stoploss: Optional[bool] = None
- timeframe: Optional[str] = None
+ trailing_stop: bool | None = None
+ trailing_stop_positive: float | None = None
+ trailing_stop_positive_offset: float | None = None
+ trailing_only_offset_is_reached: bool | None = None
+ unfilledtimeout: UnfilledTimeout | None = None # Empty in webserver mode
+ order_types: OrderTypes | None = None
+ use_custom_stoploss: bool | None = None
+ timeframe: str | None = None
timeframe_ms: int
timeframe_min: int
exchange: str
- strategy: Optional[str] = None
+ strategy: str | None = None
force_entry_enable: bool
exit_pricing: dict[str, Any]
entry_pricing: dict[str, Any]
@@ -250,18 +250,18 @@ class OrderSchema(BaseModel):
pair: str
order_id: str
status: str
- remaining: Optional[float] = None
+ remaining: float | None = None
amount: float
safe_price: float
cost: float
- filled: Optional[float] = None
+ filled: float | None = None
ft_order_side: str
order_type: str
is_open: bool
- order_timestamp: Optional[int] = None
- order_filled_timestamp: Optional[int] = None
- ft_fee_base: Optional[float] = None
- ft_order_tag: Optional[str] = None
+ order_timestamp: int | None = None
+ order_filled_timestamp: int | None = None
+ ft_fee_base: float | None = None
+ ft_order_tag: str | None = None
class TradeSchema(BaseModel):
@@ -275,80 +275,80 @@ class TradeSchema(BaseModel):
amount: float
amount_requested: float
stake_amount: float
- max_stake_amount: Optional[float] = None
+ max_stake_amount: float | None = None
strategy: str
- enter_tag: Optional[str] = None
+ enter_tag: str | None = None
timeframe: int
- fee_open: Optional[float] = None
- fee_open_cost: Optional[float] = None
- fee_open_currency: Optional[str] = None
- fee_close: Optional[float] = None
- fee_close_cost: Optional[float] = None
- fee_close_currency: Optional[str] = None
+ fee_open: float | None = None
+ fee_open_cost: float | None = None
+ fee_open_currency: str | None = None
+ fee_close: float | None = None
+ fee_close_cost: float | None = None
+ fee_close_currency: str | None = None
open_date: str
open_timestamp: int
- open_fill_date: Optional[str]
- open_fill_timestamp: Optional[int]
+ open_fill_date: str | None
+ open_fill_timestamp: int | None
open_rate: float
- open_rate_requested: Optional[float] = None
+ open_rate_requested: float | None = None
open_trade_value: float
- close_date: Optional[str] = None
- close_timestamp: Optional[int] = None
- close_rate: Optional[float] = None
- close_rate_requested: Optional[float] = None
+ close_date: str | None = None
+ close_timestamp: int | None = None
+ close_rate: float | None = None
+ close_rate_requested: float | None = None
- close_profit: Optional[float] = None
- close_profit_pct: Optional[float] = None
- close_profit_abs: Optional[float] = None
+ close_profit: float | None = None
+ close_profit_pct: float | None = None
+ close_profit_abs: float | None = None
- profit_ratio: Optional[float] = None
- profit_pct: Optional[float] = None
- profit_abs: Optional[float] = None
- profit_fiat: Optional[float] = None
+ profit_ratio: float | None = None
+ profit_pct: float | None = None
+ profit_abs: float | None = None
+ profit_fiat: float | None = None
realized_profit: float
- realized_profit_ratio: Optional[float] = None
+ realized_profit_ratio: float | None = None
- exit_reason: Optional[str] = None
- exit_order_status: Optional[str] = None
+ exit_reason: str | None = None
+ exit_order_status: str | None = None
- stop_loss_abs: Optional[float] = None
- stop_loss_ratio: Optional[float] = None
- stop_loss_pct: Optional[float] = None
- stoploss_last_update: Optional[str] = None
- stoploss_last_update_timestamp: Optional[int] = None
- initial_stop_loss_abs: Optional[float] = None
- initial_stop_loss_ratio: Optional[float] = None
- initial_stop_loss_pct: Optional[float] = None
+ stop_loss_abs: float | None = None
+ stop_loss_ratio: float | None = None
+ stop_loss_pct: float | None = None
+ stoploss_last_update: str | None = None
+ stoploss_last_update_timestamp: int | None = None
+ initial_stop_loss_abs: float | None = None
+ initial_stop_loss_ratio: float | None = None
+ initial_stop_loss_pct: float | None = None
- min_rate: Optional[float] = None
- max_rate: Optional[float] = None
+ min_rate: float | None = None
+ max_rate: float | None = None
has_open_orders: bool
orders: list[OrderSchema]
- leverage: Optional[float] = None
- interest_rate: Optional[float] = None
- liquidation_price: Optional[float] = None
- funding_fees: Optional[float] = None
- trading_mode: Optional[TradingMode] = None
+ leverage: float | None = None
+ interest_rate: float | None = None
+ liquidation_price: float | None = None
+ funding_fees: float | None = None
+ trading_mode: TradingMode | None = None
- amount_precision: Optional[float] = None
- price_precision: Optional[float] = None
- precision_mode: Optional[int] = None
+ amount_precision: float | None = None
+ price_precision: float | None = None
+ precision_mode: int | None = None
class OpenTradeSchema(TradeSchema):
- stoploss_current_dist: Optional[float] = None
- stoploss_current_dist_pct: Optional[float] = None
- stoploss_current_dist_ratio: Optional[float] = None
- stoploss_entry_dist: Optional[float] = None
- stoploss_entry_dist_ratio: Optional[float] = None
+ stoploss_current_dist: float | None = None
+ stoploss_current_dist_pct: float | None = None
+ stoploss_current_dist_ratio: float | None = None
+ stoploss_entry_dist: float | None = None
+ stoploss_entry_dist_ratio: float | None = None
current_rate: float
total_profit_abs: float
- total_profit_fiat: Optional[float] = None
- total_profit_ratio: Optional[float] = None
+ total_profit_fiat: float | None = None
+ total_profit_ratio: float | None = None
class TradeResponse(BaseModel):
@@ -358,7 +358,7 @@ class TradeResponse(BaseModel):
total_trades: int
-ForceEnterResponse = RootModel[Union[TradeSchema, StatusMsg]]
+ForceEnterResponse = RootModel[TradeSchema | StatusMsg]
class LockModel(BaseModel):
@@ -370,7 +370,7 @@ class LockModel(BaseModel):
lock_timestamp: int
pair: str
side: str
- reason: Optional[str] = None
+ reason: str | None = None
class Locks(BaseModel):
@@ -382,12 +382,12 @@ class LocksPayload(BaseModel):
pair: str
side: str = "*" # Default to both sides
until: AwareDatetime
- reason: Optional[str] = None
+ reason: str | None = None
class DeleteLockRequest(BaseModel):
- pair: Optional[str] = None
- lockid: Optional[int] = None
+ pair: str | None = None
+ lockid: int | None = None
class Logs(BaseModel):
@@ -398,17 +398,17 @@ class Logs(BaseModel):
class ForceEnterPayload(BaseModel):
pair: str
side: SignalDirection = SignalDirection.LONG
- price: Optional[float] = None
- ordertype: Optional[OrderTypeValues] = None
- stakeamount: Optional[float] = None
- entry_tag: Optional[str] = None
- leverage: Optional[float] = None
+ price: float | None = None
+ ordertype: OrderTypeValues | None = None
+ stakeamount: float | None = None
+ entry_tag: str | None = None
+ leverage: float | None = None
class ForceExitPayload(BaseModel):
- tradeid: Union[str, int]
- ordertype: Optional[OrderTypeValues] = None
- amount: Optional[float] = None
+ tradeid: str | int
+ ordertype: OrderTypeValues | None = None
+ amount: float | None = None
class BlacklistPayload(BaseModel):
@@ -430,7 +430,7 @@ class WhitelistResponse(BaseModel):
class WhitelistEvaluateResponse(BackgroundTaskResult):
- result: Optional[WhitelistResponse] = None
+ result: WhitelistResponse | None = None
class DeleteTrade(BaseModel):
@@ -445,7 +445,7 @@ class PlotConfig_(BaseModel):
subplots: dict[str, Any]
-PlotConfig = RootModel[Union[PlotConfig_, dict]]
+PlotConfig = RootModel[PlotConfig_ | dict]
class StrategyListResponse(BaseModel):
@@ -489,7 +489,7 @@ class FreqAIModelListResponse(BaseModel):
class StrategyResponse(BaseModel):
strategy: str
code: str
- timeframe: Optional[str]
+ timeframe: str | None
class AvailablePairs(BaseModel):
@@ -501,14 +501,14 @@ class AvailablePairs(BaseModel):
class PairCandlesRequest(BaseModel):
pair: str
timeframe: str
- limit: Optional[int] = None
- columns: Optional[list[str]] = None
+ limit: int | None = None
+ columns: list[str] | None = None
class PairHistoryRequest(PairCandlesRequest):
timerange: str
strategy: str
- freqaimodel: Optional[str] = None
+ freqaimodel: str | None = None
class PairHistory(BaseModel):
@@ -540,16 +540,16 @@ class BacktestFreqAIInputs(BaseModel):
class BacktestRequest(BaseModel):
strategy: str
- timeframe: Optional[str] = None
- timeframe_detail: Optional[str] = None
- timerange: Optional[str] = None
- max_open_trades: Optional[IntOrInf] = None
- stake_amount: Optional[Union[str, float]] = None
+ timeframe: str | None = None
+ timeframe_detail: str | None = None
+ timerange: str | None = None
+ max_open_trades: IntOrInf | None = None
+ stake_amount: str | float | None = None
enable_protections: bool
- dry_run_wallet: Optional[float] = None
- backtest_cache: Optional[str] = None
- freqaimodel: Optional[str] = None
- freqai: Optional[BacktestFreqAIInputs] = None
+ dry_run_wallet: float | None = None
+ backtest_cache: str | None = None
+ freqaimodel: str | None = None
+ freqai: BacktestFreqAIInputs | None = None
class BacktestResponse(BaseModel):
@@ -558,9 +558,9 @@ class BacktestResponse(BaseModel):
status_msg: str
step: str
progress: float
- trade_count: Optional[float] = None
+ trade_count: float | None = None
# TODO: Properly type backtestresult...
- backtest_result: Optional[dict[str, Any]] = None
+ backtest_result: dict[str, Any] | None = None
# TODO: This is a copy of BacktestHistoryEntryType
@@ -569,11 +569,11 @@ class BacktestHistoryEntry(BaseModel):
strategy: str
run_id: str
backtest_start_time: int
- notes: Optional[str] = ""
- backtest_start_ts: Optional[int] = None
- backtest_end_ts: Optional[int] = None
- timeframe: Optional[str] = None
- timeframe_detail: Optional[str] = None
+ notes: str | None = ""
+ backtest_start_ts: int | None = None
+ backtest_end_ts: int | None = None
+ timeframe: str | None = None
+ timeframe_detail: str | None = None
class BacktestMetadataUpdate(BaseModel):
@@ -593,9 +593,9 @@ class SysInfo(BaseModel):
class Health(BaseModel):
- last_process: Optional[datetime] = None
- last_process_ts: Optional[int] = None
- bot_start: Optional[datetime] = None
- bot_start_ts: Optional[int] = None
- bot_startup: Optional[datetime] = None
- bot_startup_ts: Optional[int] = None
+ last_process: datetime | None = None
+ last_process_ts: int | None = None
+ bot_start: datetime | None = None
+ bot_start_ts: int | None = None
+ bot_startup: datetime | None = None
+ bot_startup_ts: int | None = None
diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py
index 936022109..fb856add4 100644
--- a/freqtrade/rpc/api_server/api_v1.py
+++ b/freqtrade/rpc/api_server/api_v1.py
@@ -1,6 +1,5 @@
import logging
from copy import deepcopy
-from typing import Optional
from fastapi import APIRouter, Depends, Query
from fastapi.exceptions import HTTPException
@@ -119,17 +118,17 @@ def count(rpc: RPC = Depends(get_rpc)):
@router.get("/entries", response_model=list[Entry], tags=["info"])
-def entries(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)):
+def entries(pair: str | None = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_enter_tag_performance(pair)
@router.get("/exits", response_model=list[Exit], tags=["info"])
-def exits(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)):
+def exits(pair: str | None = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_exit_reason_performance(pair)
@router.get("/mix_tags", response_model=list[MixTag], tags=["info"])
-def mix_tags(pair: Optional[str] = None, rpc: RPC = Depends(get_rpc)):
+def mix_tags(pair: str | None = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_mix_tag_performance(pair)
@@ -216,7 +215,7 @@ def edge(rpc: RPC = Depends(get_rpc)):
@router.get("/show_config", response_model=ShowConfig, tags=["info"])
-def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(get_config)):
+def show_config(rpc: RPC | None = Depends(get_rpc_optional), config=Depends(get_config)):
state = ""
strategy_version = None
if rpc:
@@ -304,7 +303,7 @@ def add_locks(payload: list[LocksPayload], rpc: RPC = Depends(get_rpc)):
@router.get("/logs", response_model=Logs, tags=["info"])
-def logs(limit: Optional[int] = None):
+def logs(limit: int | None = None):
return RPC._rpc_get_logs(limit)
@@ -330,9 +329,7 @@ def reload_config(rpc: RPC = Depends(get_rpc)):
@router.get("/pair_candles", response_model=PairHistory, tags=["candle data"])
-def pair_candles(
- pair: str, timeframe: str, limit: Optional[int] = None, rpc: RPC = Depends(get_rpc)
-):
+def pair_candles(pair: str, timeframe: str, limit: int | None = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_analysed_dataframe(pair, timeframe, limit, None)
@@ -350,7 +347,7 @@ def pair_history(
timeframe: str,
timerange: str,
strategy: str,
- freqaimodel: Optional[str] = None,
+ freqaimodel: str | None = None,
config=Depends(get_config),
exchange=Depends(get_exchange),
):
@@ -396,9 +393,9 @@ def pair_history_filtered(
@router.get("/plot_config", response_model=PlotConfig, tags=["candle data"])
def plot_config(
- strategy: Optional[str] = None,
+ strategy: str | None = None,
config=Depends(get_config),
- rpc: Optional[RPC] = Depends(get_rpc_optional),
+ rpc: RPC | None = Depends(get_rpc_optional),
):
if not strategy:
if not rpc:
@@ -494,9 +491,9 @@ def list_freqaimodels(config=Depends(get_config)):
@router.get("/available_pairs", response_model=AvailablePairs, tags=["candle data"])
def list_available_pairs(
- timeframe: Optional[str] = None,
- stake_currency: Optional[str] = None,
- candletype: Optional[CandleType] = None,
+ timeframe: str | None = None,
+ stake_currency: str | None = None,
+ candletype: CandleType | None = None,
config=Depends(get_config),
):
dh = get_datahandler(config["datadir"], config.get("dataformat_ohlcv"))
diff --git a/freqtrade/rpc/api_server/deps.py b/freqtrade/rpc/api_server/deps.py
index 997e8487b..74ee82a8c 100644
--- a/freqtrade/rpc/api_server/deps.py
+++ b/freqtrade/rpc/api_server/deps.py
@@ -1,5 +1,5 @@
from collections.abc import AsyncIterator
-from typing import Any, Optional
+from typing import Any
from uuid import uuid4
from fastapi import Depends, HTTPException
@@ -14,13 +14,13 @@ from freqtrade.rpc.rpc import RPC, RPCException
from .webserver import ApiServer
-def get_rpc_optional() -> Optional[RPC]:
+def get_rpc_optional() -> RPC | None:
if ApiServer._has_rpc:
return ApiServer._rpc
return None
-async def get_rpc() -> Optional[AsyncIterator[RPC]]:
+async def get_rpc() -> AsyncIterator[RPC] | None:
_rpc = get_rpc_optional()
if _rpc:
request_id = str(uuid4())
diff --git a/freqtrade/rpc/api_server/web_ui.py b/freqtrade/rpc/api_server/web_ui.py
index bf37c6fa7..0a8a73011 100644
--- a/freqtrade/rpc/api_server/web_ui.py
+++ b/freqtrade/rpc/api_server/web_ui.py
@@ -1,5 +1,4 @@
from pathlib import Path
-from typing import Optional
from fastapi import APIRouter
from fastapi.exceptions import HTTPException
@@ -42,7 +41,7 @@ async def index_html(rest_of_path: str):
filename = uibase / rest_of_path
# It's security relevant to check "relative_to".
# Without this, Directory-traversal is possible.
- media_type: Optional[str] = None
+ media_type: str | None = None
if filename.suffix == ".js":
# Force text/javascript for .js files - Circumvent faulty system configuration
media_type = "application/javascript"
diff --git a/freqtrade/rpc/api_server/webserver.py b/freqtrade/rpc/api_server/webserver.py
index c96db9981..aa3825df3 100644
--- a/freqtrade/rpc/api_server/webserver.py
+++ b/freqtrade/rpc/api_server/webserver.py
@@ -1,6 +1,6 @@
import logging
from ipaddress import ip_address
-from typing import Any, Optional
+from typing import Any
import orjson
import uvicorn
@@ -39,7 +39,7 @@ class ApiServer(RPCHandler):
_has_rpc: bool = False
_config: Config = {}
# websocket message stuff
- _message_stream: Optional[MessageStream] = None
+ _message_stream: MessageStream | None = None
def __new__(cls, *args, **kwargs):
"""
diff --git a/freqtrade/rpc/api_server/webserver_bgwork.py b/freqtrade/rpc/api_server/webserver_bgwork.py
index dc8222490..642da632e 100644
--- a/freqtrade/rpc/api_server/webserver_bgwork.py
+++ b/freqtrade/rpc/api_server/webserver_bgwork.py
@@ -1,4 +1,4 @@
-from typing import Any, Literal, Optional, TypedDict
+from typing import Any, Literal, TypedDict
from uuid import uuid4
from freqtrade.exchange.exchange import Exchange
@@ -8,9 +8,9 @@ class JobsContainer(TypedDict):
category: Literal["pairlist"]
is_running: bool
status: str
- progress: Optional[float]
+ progress: float | None
result: Any
- error: Optional[str]
+ error: str | None
class ApiBG:
diff --git a/freqtrade/rpc/api_server/ws/channel.py b/freqtrade/rpc/api_server/ws/channel.py
index 5acbebe9f..d05a1c1f7 100644
--- a/freqtrade/rpc/api_server/ws/channel.py
+++ b/freqtrade/rpc/api_server/ws/channel.py
@@ -4,7 +4,7 @@ import time
from collections import deque
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
-from typing import Any, Optional, Union
+from typing import Any
from uuid import uuid4
from fastapi import WebSocketDisconnect
@@ -30,7 +30,7 @@ class WebSocketChannel:
def __init__(
self,
websocket: WebSocketType,
- channel_id: Optional[str] = None,
+ channel_id: str | None = None,
serializer_cls: type[WebSocketSerializer] = HybridJSONWebSocketSerializer,
send_throttle: float = 0.01,
):
@@ -80,9 +80,7 @@ class WebSocketChannel:
# maximum of 3 seconds per message
self._send_high_limit = min(max(self.avg_send_time * 2, 1), 3)
- async def send(
- self, message: Union[WSMessageSchemaType, dict[str, Any]], use_timeout: bool = False
- ):
+ async def send(self, message: WSMessageSchemaType | dict[str, Any], use_timeout: bool = False):
"""
Send a message on the wrapped websocket. If the sending
takes too long, it will raise a TimeoutError and
diff --git a/freqtrade/rpc/api_server/ws/proxy.py b/freqtrade/rpc/api_server/ws/proxy.py
index a2c2cbafc..c32494176 100644
--- a/freqtrade/rpc/api_server/ws/proxy.py
+++ b/freqtrade/rpc/api_server/ws/proxy.py
@@ -1,7 +1,7 @@
-from typing import Any, Union
+from typing import Any
from fastapi import WebSocket as FastAPIWebSocket
-from websockets.client import WebSocketClientProtocol as WebSocket
+from websockets.asyncio.client import ClientConnection as WebSocket
from freqtrade.rpc.api_server.ws.types import WebSocketType
@@ -13,7 +13,7 @@ class WebSocketProxy:
"""
def __init__(self, websocket: WebSocketType):
- self._websocket: Union[FastAPIWebSocket, WebSocket] = websocket
+ self._websocket: FastAPIWebSocket | WebSocket = websocket
@property
def raw_websocket(self):
diff --git a/freqtrade/rpc/api_server/ws/serializer.py b/freqtrade/rpc/api_server/ws/serializer.py
index a93a776cc..cee7da124 100644
--- a/freqtrade/rpc/api_server/ws/serializer.py
+++ b/freqtrade/rpc/api_server/ws/serializer.py
@@ -1,6 +1,6 @@
import logging
from abc import ABC, abstractmethod
-from typing import Any, Union
+from typing import Any
import orjson
import rapidjson
@@ -26,7 +26,7 @@ class WebSocketSerializer(ABC):
def _deserialize(self, data):
raise NotImplementedError()
- async def send(self, data: Union[WSMessageSchemaType, dict[str, Any]]):
+ async def send(self, data: WSMessageSchemaType | dict[str, Any]):
await self._websocket.send(self._serialize(data))
async def recv(self) -> bytes:
diff --git a/freqtrade/rpc/api_server/ws/types.py b/freqtrade/rpc/api_server/ws/types.py
index 8f7dad33b..7ce2eb4bf 100644
--- a/freqtrade/rpc/api_server/ws/types.py
+++ b/freqtrade/rpc/api_server/ws/types.py
@@ -1,7 +1,7 @@
from typing import Any, TypeVar
from fastapi import WebSocket as FastAPIWebSocket
-from websockets.client import WebSocketClientProtocol as WebSocket
+from websockets.asyncio.client import ClientConnection as WebSocket
WebSocketType = TypeVar("WebSocketType", FastAPIWebSocket, WebSocket)
diff --git a/freqtrade/rpc/api_server/ws_schemas.py b/freqtrade/rpc/api_server/ws_schemas.py
index 5eb8f1812..3f9e80eea 100644
--- a/freqtrade/rpc/api_server/ws_schemas.py
+++ b/freqtrade/rpc/api_server/ws_schemas.py
@@ -1,5 +1,5 @@
from datetime import datetime
-from typing import Any, Optional, TypedDict
+from typing import Any, TypedDict
from pandas import DataFrame
from pydantic import BaseModel, ConfigDict
@@ -14,18 +14,18 @@ class BaseArbitraryModel(BaseModel):
class WSRequestSchema(BaseArbitraryModel):
type: RPCRequestType
- data: Optional[Any] = None
+ data: Any | None = None
class WSMessageSchemaType(TypedDict):
# Type for typing to avoid doing pydantic typechecks.
type: RPCMessageType
- data: Optional[dict[str, Any]]
+ data: dict[str, Any] | None
class WSMessageSchema(BaseArbitraryModel):
type: RPCMessageType
- data: Optional[Any] = None
+ data: Any | None = None
model_config = ConfigDict(extra="allow")
diff --git a/freqtrade/rpc/external_message_consumer.py b/freqtrade/rpc/external_message_consumer.py
index e08553fab..fe22613bd 100644
--- a/freqtrade/rpc/external_message_consumer.py
+++ b/freqtrade/rpc/external_message_consumer.py
@@ -8,8 +8,9 @@ from it
import asyncio
import logging
import socket
+from collections.abc import Callable
from threading import Thread
-from typing import Any, Callable, TypedDict, Union
+from typing import Any, TypedDict
import websockets
from pydantic import ValidationError
@@ -42,7 +43,7 @@ class Producer(TypedDict):
logger = logging.getLogger(__name__)
-def schema_to_dict(schema: Union[WSMessageSchema, WSRequestSchema]):
+def schema_to_dict(schema: WSMessageSchema | WSRequestSchema):
return schema.model_dump(exclude_none=True)
@@ -212,8 +213,7 @@ class ExternalMessageConsumer:
except (
socket.gaierror,
ConnectionRefusedError,
- websockets.exceptions.InvalidStatusCode,
- websockets.exceptions.InvalidMessage,
+ websockets.exceptions.InvalidHandshake,
) as e:
logger.error(f"Connection Refused - {e} retrying in {self.sleep_time}s")
await asyncio.sleep(self.sleep_time)
@@ -282,9 +282,7 @@ class ExternalMessageConsumer:
logger.debug(e, exc_info=e)
raise
- def send_producer_request(
- self, producer_name: str, request: Union[WSRequestSchema, dict[str, Any]]
- ):
+ def send_producer_request(self, producer_name: str, request: WSRequestSchema | dict[str, Any]):
"""
Publish a message to the producer's message stream to be
sent by the channel task.
diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index c3bcfd9f1..497f1d133 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -7,7 +7,7 @@ from abc import abstractmethod
from collections.abc import Generator, Sequence
from datetime import date, datetime, timedelta, timezone
from math import isnan
-from typing import Any, Optional, Union
+from typing import Any
import psutil
from dateutil.relativedelta import relativedelta
@@ -97,7 +97,7 @@ class RPC:
"""
# Bind _fiat_converter if needed
- _fiat_converter: Optional[CryptoToFiatConverter] = None
+ _fiat_converter: CryptoToFiatConverter | None = None
def __init__(self, freqtrade) -> None:
"""
@@ -112,7 +112,7 @@ class RPC:
@staticmethod
def _rpc_show_config(
- config, botstate: Union[State, str], strategy_version: Optional[str] = None
+ config, botstate: State | str, strategy_version: str | None = None
) -> dict[str, Any]:
"""
Return a dict of config options.
@@ -168,7 +168,7 @@ class RPC:
}
return val
- def _rpc_trade_status(self, trade_ids: Optional[list[int]] = None) -> list[dict[str, Any]]:
+ def _rpc_trade_status(self, trade_ids: list[int] | None = None) -> list[dict[str, Any]]:
"""
Below follows the RPC backend it is prefixed with rpc_ to raise awareness that it is
a remotely exposed function
@@ -184,11 +184,11 @@ class RPC:
else:
results = []
for trade in trades:
- current_profit_fiat: Optional[float] = None
- total_profit_fiat: Optional[float] = None
+ current_profit_fiat: float | None = None
+ total_profit_fiat: float | None = None
# prepare open orders details
- oo_details: Optional[str] = ""
+ oo_details: str | None = ""
oo_details_lst = [
f"({oo.order_type} {oo.side} rem={oo.safe_remaining:.8f})"
for oo in trade.open_orders
@@ -197,7 +197,7 @@ class RPC:
oo_details = ", ".join(oo_details_lst)
total_profit_abs = 0.0
- total_profit_ratio: Optional[float] = None
+ total_profit_ratio: float | None = None
# calculate profit and send message to user
if trade.is_open:
try:
@@ -487,7 +487,7 @@ class RPC:
return {"exit_reasons": exit_reasons, "durations": durations}
def _rpc_trade_statistics(
- self, stake_currency: str, fiat_display_currency: str, start_date: Optional[datetime] = None
+ self, stake_currency: str, fiat_display_currency: str, start_date: datetime | None = None
) -> dict[str, Any]:
"""Returns cumulative profit statistics"""
@@ -682,7 +682,7 @@ class RPC:
est_bot_stake = amount
else:
pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency)
- rate: Optional[float] = tickers.get(pair, {}).get("last", None)
+ rate: float | None = tickers.get(pair, {}).get("last", None)
if rate:
if pair.startswith(stake_currency) and not pair.endswith(stake_currency):
rate = 1.0 / rate
@@ -851,7 +851,7 @@ class RPC:
return {"status": "Reloaded from orders from exchange"}
def __exec_force_exit(
- self, trade: Trade, ordertype: Optional[str], amount: Optional[float] = None
+ self, trade: Trade, ordertype: str | None, amount: float | None = None
) -> bool:
# Check if there is there are open orders
trade_entry_cancelation_registry = []
@@ -882,7 +882,7 @@ class RPC:
order_type = ordertype or self._freqtrade.strategy.order_types.get(
"force_exit", self._freqtrade.strategy.order_types["exit"]
)
- sub_amount: Optional[float] = None
+ sub_amount: float | None = None
if amount and amount < trade.amount:
# Partial exit ...
min_exit_stake = self._freqtrade.exchange.get_min_pair_stake_amount(
@@ -901,7 +901,7 @@ class RPC:
return False
def _rpc_force_exit(
- self, trade_id: str, ordertype: Optional[str] = None, *, amount: Optional[float] = None
+ self, trade_id: str, ordertype: str | None = None, *, amount: float | None = None
) -> dict[str, str]:
"""
Handler for forceexit .
@@ -960,14 +960,14 @@ class RPC:
def _rpc_force_entry(
self,
pair: str,
- price: Optional[float],
+ price: float | None,
*,
- order_type: Optional[str] = None,
+ order_type: str | None = None,
order_side: SignalDirection = SignalDirection.LONG,
- stake_amount: Optional[float] = None,
- enter_tag: Optional[str] = "force_entry",
- leverage: Optional[float] = None,
- ) -> Optional[Trade]:
+ stake_amount: float | None = None,
+ enter_tag: str | None = "force_entry",
+ leverage: float | None = None,
+ ) -> Trade | None:
"""
Handler for forcebuy
Buys a pair trade at the given or current price
@@ -977,7 +977,7 @@ class RPC:
# check if valid pair
# check if pair already has an open pair
- trade: Optional[Trade] = Trade.get_trades(
+ trade: Trade | None = Trade.get_trades(
[Trade.is_open.is_(True), Trade.pair == pair]
).first()
is_short = order_side == SignalDirection.SHORT
@@ -1052,7 +1052,7 @@ class RPC:
)
Trade.commit()
- def _rpc_delete(self, trade_id: int) -> dict[str, Union[str, int]]:
+ def _rpc_delete(self, trade_id: int) -> dict[str, str | int]:
"""
Handler for delete .
Delete the given trade and close eventually existing open orders.
@@ -1093,7 +1093,7 @@ class RPC:
"cancel_order_count": c_count,
}
- def _rpc_list_custom_data(self, trade_id: int, key: Optional[str]) -> list[dict[str, Any]]:
+ def _rpc_list_custom_data(self, trade_id: int, key: str | None) -> list[dict[str, Any]]:
# Query for trade
trade = Trade.get_trades(trade_filter=[Trade.id == trade_id]).first()
if trade is None:
@@ -1128,21 +1128,21 @@ class RPC:
return pair_rates
- def _rpc_enter_tag_performance(self, pair: Optional[str]) -> list[dict[str, Any]]:
+ def _rpc_enter_tag_performance(self, pair: str | None) -> list[dict[str, Any]]:
"""
Handler for buy tag performance.
Shows a performance statistic from finished trades
"""
return Trade.get_enter_tag_performance(pair)
- def _rpc_exit_reason_performance(self, pair: Optional[str]) -> list[dict[str, Any]]:
+ def _rpc_exit_reason_performance(self, pair: str | None) -> list[dict[str, Any]]:
"""
Handler for exit reason performance.
Shows a performance statistic from finished trades
"""
return Trade.get_exit_reason_performance(pair)
- def _rpc_mix_tag_performance(self, pair: Optional[str]) -> list[dict[str, Any]]:
+ def _rpc_mix_tag_performance(self, pair: str | None) -> list[dict[str, Any]]:
"""
Handler for mix tag (enter_tag + exit_reason) performance.
Shows a performance statistic from finished trades
@@ -1174,7 +1174,7 @@ class RPC:
return {"lock_count": len(locks), "locks": [lock.to_json() for lock in locks]}
def _rpc_delete_lock(
- self, lockid: Optional[int] = None, pair: Optional[str] = None
+ self, lockid: int | None = None, pair: str | None = None
) -> dict[str, Any]:
"""Delete specific lock(s)"""
locks: Sequence[PairLock] = []
@@ -1192,9 +1192,7 @@ class RPC:
return self._rpc_locks()
- def _rpc_add_lock(
- self, pair: str, until: datetime, reason: Optional[str], side: str
- ) -> PairLock:
+ def _rpc_add_lock(self, pair: str, until: datetime, reason: str | None, side: str) -> PairLock:
lock = PairLocks.lock_pair(
pair=pair,
until=until,
@@ -1224,7 +1222,7 @@ class RPC:
resp["errors"] = errors
return resp
- def _rpc_blacklist(self, add: Optional[list[str]] = None) -> dict:
+ def _rpc_blacklist(self, add: list[str] | None = None) -> dict:
"""Returns the currently active blacklist"""
errors = {}
if add:
@@ -1249,7 +1247,7 @@ class RPC:
return res
@staticmethod
- def _rpc_get_logs(limit: Optional[int]) -> dict[str, Any]:
+ def _rpc_get_logs(limit: int | None) -> dict[str, Any]:
"""Returns the last X logs"""
if limit:
buffer = bufferHandler.buffer[-limit:]
@@ -1286,7 +1284,7 @@ class RPC:
timeframe: str,
dataframe: DataFrame,
last_analyzed: datetime,
- selected_cols: Optional[list[str]],
+ selected_cols: list[str] | None,
) -> dict[str, Any]:
has_content = len(dataframe) != 0
dataframe_columns = list(dataframe.columns)
@@ -1355,7 +1353,7 @@ class RPC:
return res
def _rpc_analysed_dataframe(
- self, pair: str, timeframe: str, limit: Optional[int], selected_cols: Optional[list[str]]
+ self, pair: str, timeframe: str, limit: int | None, selected_cols: list[str] | None
) -> dict[str, Any]:
"""Analyzed dataframe in Dict form"""
@@ -1365,7 +1363,7 @@ class RPC:
)
def __rpc_analysed_dataframe_raw(
- self, pair: str, timeframe: str, limit: Optional[int]
+ self, pair: str, timeframe: str, limit: int | None
) -> tuple[DataFrame, datetime]:
"""
Get the dataframe and last analyze from the dataprovider
@@ -1383,7 +1381,7 @@ class RPC:
return _data, last_analyzed
def _ws_all_analysed_dataframes(
- self, pairlist: list[str], limit: Optional[int]
+ self, pairlist: list[str], limit: int | None
) -> Generator[dict[str, Any], None, None]:
"""
Get the analysed dataframes of each pair in the pairlist.
@@ -1403,7 +1401,7 @@ class RPC:
yield {"key": (pair, timeframe, candle_type), "df": dataframe, "la": last_analyzed}
- def _ws_request_analyzed_df(self, limit: Optional[int] = None, pair: Optional[str] = None):
+ def _ws_request_analyzed_df(self, limit: int | None = None, pair: str | None = None):
"""Historical Analyzed Dataframes for WebSocket"""
pairlist = [pair] if pair else self._freqtrade.active_pair_whitelist
@@ -1415,7 +1413,7 @@ class RPC:
@staticmethod
def _rpc_analysed_history_full(
- config: Config, pair: str, timeframe: str, exchange, selected_cols: Optional[list[str]]
+ config: Config, pair: str, timeframe: str, exchange, selected_cols: list[str] | None
) -> dict[str, Any]:
timerange_parsed = TimeRange.parse_timerange(config.get("timerange"))
@@ -1482,9 +1480,9 @@ class RPC:
"ram_pct": psutil.virtual_memory().percent,
}
- def health(self) -> dict[str, Optional[Union[str, int]]]:
+ def health(self) -> dict[str, str | int | None]:
last_p = self._freqtrade.last_process
- res: dict[str, Union[None, str, int]] = {
+ res: dict[str, None | str | int] = {
"last_process": None,
"last_process_loc": None,
"last_process_ts": None,
diff --git a/freqtrade/rpc/rpc_types.py b/freqtrade/rpc/rpc_types.py
index 0fa4cea75..ad6138a91 100644
--- a/freqtrade/rpc/rpc_types.py
+++ b/freqtrade/rpc/rpc_types.py
@@ -1,5 +1,5 @@
from datetime import datetime
-from typing import Any, Literal, Optional, TypedDict, Union
+from typing import Any, Literal, TypedDict
from freqtrade.constants import PairWithTimeframe
from freqtrade.enums import RPCMessageType
@@ -31,7 +31,7 @@ class RPCProtectionMsg(RPCSendMsgBase):
type: Literal[RPCMessageType.PROTECTION_TRIGGER, RPCMessageType.PROTECTION_TRIGGER_GLOBAL]
id: int
pair: str
- base_currency: Optional[str]
+ base_currency: str | None
lock_time: str
lock_timestamp: int
lock_end_time: str
@@ -48,23 +48,23 @@ class RPCWhitelistMsg(RPCSendMsgBase):
class __RPCEntryExitMsgBase(RPCSendMsgBase):
trade_id: int
- buy_tag: Optional[str]
- enter_tag: Optional[str]
+ buy_tag: str | None
+ enter_tag: str | None
exchange: str
pair: str
base_currency: str
quote_currency: str
- leverage: Optional[float]
+ leverage: float | None
direction: str
limit: float
open_rate: float
order_type: str
stake_amount: float
stake_currency: str
- fiat_currency: Optional[str]
+ fiat_currency: str | None
amount: float
open_date: datetime
- current_rate: Optional[float]
+ current_rate: float | None
sub_trade: bool
@@ -84,11 +84,11 @@ class RPCExitMsg(__RPCEntryExitMsgBase):
close_rate: float
profit_amount: float
profit_ratio: float
- exit_reason: Optional[str]
+ exit_reason: str | None
close_date: datetime
- # current_rate: Optional[float]
- order_rate: Optional[float]
- final_profit_ratio: Optional[float]
+ # current_rate: float | None
+ order_rate: float | None
+ final_profit_ratio: float | None
is_final_exit: bool
@@ -98,7 +98,7 @@ class RPCExitCancelMsg(__RPCEntryExitMsgBase):
gain: ProfitLossStr
profit_amount: float
profit_ratio: float
- exit_reason: Optional[str]
+ exit_reason: str | None
close_date: datetime
@@ -122,18 +122,18 @@ class RPCNewCandleMsg(RPCSendMsgBase):
data: PairWithTimeframe
-RPCOrderMsg = Union[RPCEntryMsg, RPCExitMsg, RPCExitCancelMsg, RPCCancelMsg]
+RPCOrderMsg = RPCEntryMsg | RPCExitMsg | RPCExitCancelMsg | RPCCancelMsg
-RPCSendMsg = Union[
- RPCStatusMsg,
- RPCStrategyMsg,
- RPCProtectionMsg,
- RPCWhitelistMsg,
- RPCEntryMsg,
- RPCCancelMsg,
- RPCExitMsg,
- RPCExitCancelMsg,
- RPCAnalyzedDFMsg,
- RPCNewCandleMsg,
-]
+RPCSendMsg = (
+ RPCStatusMsg
+ | RPCStrategyMsg
+ | RPCProtectionMsg
+ | RPCWhitelistMsg
+ | RPCEntryMsg
+ | RPCCancelMsg
+ | RPCExitMsg
+ | RPCExitCancelMsg
+ | RPCAnalyzedDFMsg
+ | RPCNewCandleMsg
+)
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 2de4499f1..77f1d1b5d 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -8,7 +8,7 @@ import asyncio
import json
import logging
import re
-from collections.abc import Coroutine
+from collections.abc import Callable, Coroutine
from copy import deepcopy
from dataclasses import dataclass
from datetime import date, datetime, timedelta
@@ -17,7 +17,7 @@ from html import escape
from itertools import chain
from math import isnan
from threading import Thread
-from typing import Any, Callable, Literal, Optional, Union
+from typing import Any, Literal
from tabulate import tabulate
from telegram import (
@@ -146,7 +146,7 @@ class Telegram(RPCHandler):
Validates the keyboard configuration from telegram config
section.
"""
- self._keyboard: list[list[Union[str, KeyboardButton]]] = [
+ self._keyboard: list[list[str | KeyboardButton]] = [
["/daily", "/profit", "/balance"],
["/status", "/status table", "/performance"],
["/count", "/start", "/stop", "/help"],
@@ -499,7 +499,7 @@ class Telegram(RPCHandler):
profit_fiat_extra = f" / {profit_fiat:.3f} {fiat_currency}"
return profit_fiat_extra
- def compose_message(self, msg: RPCSendMsg) -> Optional[str]:
+ def compose_message(self, msg: RPCSendMsg) -> str | None:
if msg["type"] == RPCMessageType.ENTRY or msg["type"] == RPCMessageType.ENTRY_FILL:
message = self._format_entry_msg(msg)
@@ -547,21 +547,22 @@ class Telegram(RPCHandler):
return None
return message
- def send_msg(self, msg: RPCSendMsg) -> None:
- """Send a message to telegram channel"""
-
+ def _message_loudness(self, msg: RPCSendMsg) -> str:
+ """Determine the loudness of the message - on, off or silent"""
default_noti = "on"
msg_type = msg["type"]
noti = ""
- if msg["type"] == RPCMessageType.EXIT:
+ if msg["type"] == RPCMessageType.EXIT or msg["type"] == RPCMessageType.EXIT_FILL:
sell_noti = (
self._config["telegram"].get("notification_settings", {}).get(str(msg_type), {})
)
+
# For backward compatibility sell still can be string
if isinstance(sell_noti, str):
noti = sell_noti
else:
+ default_noti = sell_noti.get("*", default_noti)
noti = sell_noti.get(str(msg["exit_reason"]), default_noti)
else:
noti = (
@@ -570,8 +571,14 @@ class Telegram(RPCHandler):
.get(str(msg_type), default_noti)
)
+ return noti
+
+ def send_msg(self, msg: RPCSendMsg) -> None:
+ """Send a message to telegram channel"""
+ noti = self._message_loudness(msg)
+
if noti == "off":
- logger.info(f"Notification '{msg_type}' not sent.")
+ logger.info(f"Notification '{msg['type']}' not sent.")
# Notification disabled
return
@@ -1301,7 +1308,7 @@ class Telegram(RPCHandler):
await query.answer()
await query.edit_message_text(text="Force exit canceled.")
return
- trade: Optional[Trade] = Trade.get_trades(trade_filter=Trade.id == trade_id).first()
+ trade: Trade | None = Trade.get_trades(trade_filter=Trade.id == trade_id).first()
await query.answer()
if trade:
await query.edit_message_text(
@@ -1311,7 +1318,7 @@ class Telegram(RPCHandler):
else:
await query.edit_message_text(text=f"Trade {trade_id} not found.")
- async def _force_enter_action(self, pair, price: Optional[float], order_side: SignalDirection):
+ async def _force_enter_action(self, pair, price: float | None, order_side: SignalDirection):
if pair != "cancel":
try:
@@ -1999,10 +2006,10 @@ class Telegram(RPCHandler):
msg: str,
parse_mode: str = ParseMode.MARKDOWN,
disable_notification: bool = False,
- keyboard: Optional[list[list[InlineKeyboardButton]]] = None,
+ keyboard: list[list[InlineKeyboardButton]] | None = None,
callback_path: str = "",
reload_able: bool = False,
- query: Optional[CallbackQuery] = None,
+ query: CallbackQuery | None = None,
) -> None:
"""
Send given markdown message
@@ -2011,7 +2018,7 @@ class Telegram(RPCHandler):
:param parse_mode: telegram parse mode
:return: None
"""
- reply_markup: Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]
+ reply_markup: InlineKeyboardMarkup | ReplyKeyboardMarkup
if query:
await self._update_msg(
query=query,
diff --git a/freqtrade/rpc/webhook.py b/freqtrade/rpc/webhook.py
index 640d5ea63..b1a96a872 100644
--- a/freqtrade/rpc/webhook.py
+++ b/freqtrade/rpc/webhook.py
@@ -4,7 +4,7 @@ This module manages webhook communication
import logging
import time
-from typing import Any, Optional
+from typing import Any
from requests import RequestException, post
@@ -44,7 +44,7 @@ class Webhook(RPCHandler):
"""
pass
- def _get_value_dict(self, msg: RPCSendMsg) -> Optional[dict[str, Any]]:
+ def _get_value_dict(self, msg: RPCSendMsg) -> dict[str, Any] | None:
whconfig = self._config["webhook"]
if msg["type"].value in whconfig:
# Explicit types should have priority
diff --git a/freqtrade/strategy/hyper.py b/freqtrade/strategy/hyper.py
index ffc4abb50..6281ab754 100644
--- a/freqtrade/strategy/hyper.py
+++ b/freqtrade/strategy/hyper.py
@@ -6,7 +6,7 @@ This module defines a base class for auto-hyperoptable strategies.
import logging
from collections.abc import Iterator
from pathlib import Path
-from typing import Any, Optional, Union
+from typing import Any
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException
@@ -39,7 +39,7 @@ class HyperStrategyMixin:
# Init/loading of parameters is done as part of ft_bot_start().
def enumerate_parameters(
- self, category: Optional[str] = None
+ self, category: str | None = None
) -> Iterator[tuple[str, BaseParameter]]:
"""
Find all optimizable parameters and return (name, attr) iterator.
@@ -190,7 +190,7 @@ class HyperStrategyMixin:
def detect_parameters(
- obj: Union[HyperStrategyMixin, type[HyperStrategyMixin]], category: str
+ obj: HyperStrategyMixin | type[HyperStrategyMixin], category: str
) -> Iterator[tuple[str, BaseParameter]]:
"""
Detect all parameters for 'category' for "obj"
diff --git a/freqtrade/strategy/informative_decorator.py b/freqtrade/strategy/informative_decorator.py
index cdb840512..69cf6f0ce 100644
--- a/freqtrade/strategy/informative_decorator.py
+++ b/freqtrade/strategy/informative_decorator.py
@@ -1,5 +1,6 @@
+from collections.abc import Callable
from dataclasses import dataclass
-from typing import Any, Callable, Optional, Union
+from typing import Any
from pandas import DataFrame
@@ -13,19 +14,19 @@ PopulateIndicators = Callable[[Any, DataFrame, dict], DataFrame]
@dataclass
class InformativeData:
- asset: Optional[str]
+ asset: str | None
timeframe: str
- fmt: Union[str, Callable[[Any], str], None]
+ fmt: str | Callable[[Any], str] | None
ffill: bool
- candle_type: Optional[CandleType]
+ candle_type: CandleType | None
def informative(
timeframe: str,
asset: str = "",
- fmt: Optional[Union[str, Callable[[Any], str]]] = None,
+ fmt: str | Callable[[Any], str] | None = None,
*,
- candle_type: Optional[Union[CandleType, str]] = None,
+ candle_type: CandleType | str | None = None,
ffill: bool = True,
) -> Callable[[PopulateIndicators], PopulateIndicators]:
"""
@@ -73,7 +74,7 @@ def informative(
return decorator
-def __get_pair_formats(market: Optional[dict[str, Any]]) -> dict[str, str]:
+def __get_pair_formats(market: dict[str, Any] | None) -> dict[str, str]:
if not market:
return {}
base = market["base"]
@@ -86,7 +87,7 @@ def __get_pair_formats(market: Optional[dict[str, Any]]) -> dict[str, str]:
}
-def _format_pair_name(config, pair: str, market: Optional[dict[str, Any]] = None) -> str:
+def _format_pair_name(config, pair: str, market: dict[str, Any] | None = None) -> str:
return pair.format(
stake_currency=config["stake_currency"],
stake=config["stake_currency"],
diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py
index a4bb007f1..1e415e9fc 100644
--- a/freqtrade/strategy/interface.py
+++ b/freqtrade/strategy/interface.py
@@ -8,7 +8,6 @@ from abc import ABC, abstractmethod
from collections import OrderedDict
from datetime import datetime, timedelta, timezone
from math import isinf, isnan
-from typing import Optional, Union
from pandas import DataFrame
@@ -75,7 +74,7 @@ class IStrategy(ABC, HyperStrategyMixin):
# trailing stoploss
trailing_stop: bool = False
- trailing_stop_positive: Optional[float] = None
+ trailing_stop_positive: float | None = None
trailing_stop_positive_offset: float = 0.0
trailing_only_offset_is_reached = False
use_custom_stoploss: bool = False
@@ -129,7 +128,7 @@ class IStrategy(ABC, HyperStrategyMixin):
# the dataprovider (dp) (access to other candles, historic data, ...)
# and wallets - access to the current balance.
dp: DataProvider
- wallets: Optional[Wallets] = None
+ wallets: Wallets | None = None
# Filled from configuration
stake_currency: str
# container variable for strategy source code
@@ -355,7 +354,7 @@ class IStrategy(ABC, HyperStrategyMixin):
rate: float,
time_in_force: str,
current_time: datetime,
- entry_tag: Optional[str],
+ entry_tag: str | None,
side: str,
**kwargs,
) -> bool:
@@ -444,7 +443,7 @@ class IStrategy(ABC, HyperStrategyMixin):
current_profit: float,
after_fill: bool,
**kwargs,
- ) -> Optional[float]:
+ ) -> float | None:
"""
Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
e.g. returning -0.05 would create a stoploss 5% below current_rate.
@@ -469,10 +468,10 @@ class IStrategy(ABC, HyperStrategyMixin):
def custom_entry_price(
self,
pair: str,
- trade: Optional[Trade],
+ trade: Trade | None,
current_time: datetime,
proposed_rate: float,
- entry_tag: Optional[str],
+ entry_tag: str | None,
side: str,
**kwargs,
) -> float:
@@ -501,7 +500,7 @@ class IStrategy(ABC, HyperStrategyMixin):
current_time: datetime,
proposed_rate: float,
current_profit: float,
- exit_tag: Optional[str],
+ exit_tag: str | None,
**kwargs,
) -> float:
"""
@@ -530,7 +529,7 @@ class IStrategy(ABC, HyperStrategyMixin):
current_rate: float,
current_profit: float,
**kwargs,
- ) -> Optional[Union[str, bool]]:
+ ) -> str | bool | None:
"""
DEPRECATED - please use custom_exit instead.
Custom exit signal logic indicating that specified position should be sold. Returning a
@@ -562,7 +561,7 @@ class IStrategy(ABC, HyperStrategyMixin):
current_rate: float,
current_profit: float,
**kwargs,
- ) -> Optional[Union[str, bool]]:
+ ) -> str | bool | None:
"""
Custom exit signal logic indicating that specified position should be sold. Returning a
string or True from this method is equal to setting exit signal on a candle at specified
@@ -591,10 +590,10 @@ class IStrategy(ABC, HyperStrategyMixin):
current_time: datetime,
current_rate: float,
proposed_stake: float,
- min_stake: Optional[float],
+ min_stake: float | None,
max_stake: float,
leverage: float,
- entry_tag: Optional[str],
+ entry_tag: str | None,
side: str,
**kwargs,
) -> float:
@@ -620,14 +619,14 @@ class IStrategy(ABC, HyperStrategyMixin):
current_time: datetime,
current_rate: float,
current_profit: float,
- min_stake: Optional[float],
+ min_stake: float | None,
max_stake: float,
current_entry_rate: float,
current_exit_rate: float,
current_entry_profit: float,
current_exit_profit: float,
**kwargs,
- ) -> Union[Optional[float], tuple[Optional[float], Optional[str]]]:
+ ) -> float | None | tuple[float | None, str | None]:
"""
Custom trade adjustment logic, returning the stake amount that a trade should be
increased or decreased.
@@ -660,12 +659,12 @@ class IStrategy(ABC, HyperStrategyMixin):
def adjust_entry_price(
self,
trade: Trade,
- order: Optional[Order],
+ order: Order | None,
pair: str,
current_time: datetime,
proposed_rate: float,
current_order_rate: float,
- entry_tag: Optional[str],
+ entry_tag: str | None,
side: str,
**kwargs,
) -> float:
@@ -701,7 +700,7 @@ class IStrategy(ABC, HyperStrategyMixin):
current_rate: float,
proposed_leverage: float,
max_leverage: float,
- entry_tag: Optional[str],
+ entry_tag: str | None,
side: str,
**kwargs,
) -> float:
@@ -732,7 +731,7 @@ class IStrategy(ABC, HyperStrategyMixin):
"""
return []
- def version(self) -> Optional[str]:
+ def version(self) -> str | None:
"""
Returns version of the strategy.
"""
@@ -743,7 +742,7 @@ class IStrategy(ABC, HyperStrategyMixin):
pair: str,
df: DataFrame,
tf: str,
- informative: Optional[DataFrame] = None,
+ informative: DataFrame | None = None,
set_generalized_indicators: bool = False,
) -> DataFrame:
"""
@@ -873,14 +872,14 @@ class IStrategy(ABC, HyperStrategyMixin):
current_time: datetime,
current_rate: float,
current_profit: float,
- min_stake: Optional[float],
+ min_stake: float | None,
max_stake: float,
current_entry_rate: float,
current_exit_rate: float,
current_entry_profit: float,
current_exit_profit: float,
**kwargs,
- ) -> tuple[Optional[float], str]:
+ ) -> tuple[float | None, str]:
"""
wrapper around adjust_trade_position to handle the return value
"""
@@ -980,7 +979,7 @@ class IStrategy(ABC, HyperStrategyMixin):
return self.__class__.__name__
def lock_pair(
- self, pair: str, until: datetime, reason: Optional[str] = None, side: str = "*"
+ self, pair: str, until: datetime, reason: str | None = None, side: str = "*"
) -> None:
"""
Locks pair until a given timestamp happens.
@@ -1014,7 +1013,7 @@ class IStrategy(ABC, HyperStrategyMixin):
PairLocks.unlock_reason(reason, datetime.now(timezone.utc))
def is_pair_locked(
- self, pair: str, *, candle_date: Optional[datetime] = None, side: str = "*"
+ self, pair: str, *, candle_date: datetime | None = None, side: str = "*"
) -> bool:
"""
Checks if a pair is currently locked
@@ -1150,7 +1149,7 @@ class IStrategy(ABC, HyperStrategyMixin):
pair: str,
timeframe: str,
dataframe: DataFrame,
- ) -> tuple[Optional[DataFrame], Optional[datetime]]:
+ ) -> tuple[DataFrame | None, datetime | None]:
"""
Calculates current signal based based on the entry order or exit order
columns of the dataframe.
@@ -1182,8 +1181,8 @@ class IStrategy(ABC, HyperStrategyMixin):
return latest, latest_date
def get_exit_signal(
- self, pair: str, timeframe: str, dataframe: DataFrame, is_short: Optional[bool] = None
- ) -> tuple[bool, bool, Optional[str]]:
+ self, pair: str, timeframe: str, dataframe: DataFrame, is_short: bool | None = None
+ ) -> tuple[bool, bool, str | None]:
"""
Calculates current exit signal based based on the dataframe
columns of the dataframe.
@@ -1219,7 +1218,7 @@ class IStrategy(ABC, HyperStrategyMixin):
pair: str,
timeframe: str,
dataframe: DataFrame,
- ) -> tuple[Optional[SignalDirection], Optional[str]]:
+ ) -> tuple[SignalDirection | None, str | None]:
"""
Calculates current entry signal based based on the dataframe signals
columns of the dataframe.
@@ -1238,8 +1237,8 @@ class IStrategy(ABC, HyperStrategyMixin):
enter_short = latest.get(SignalType.ENTER_SHORT.value, 0) == 1
exit_short = latest.get(SignalType.EXIT_SHORT.value, 0) == 1
- enter_signal: Optional[SignalDirection] = None
- enter_tag: Optional[str] = None
+ enter_signal: SignalDirection | None = None
+ enter_tag: str | None = None
if enter_long == 1 and not any([exit_long, enter_short]):
enter_signal = SignalDirection.LONG
enter_tag = latest.get(SignalTagType.ENTER_TAG.value, None)
@@ -1287,8 +1286,8 @@ class IStrategy(ABC, HyperStrategyMixin):
*,
enter: bool,
exit_: bool,
- low: Optional[float] = None,
- high: Optional[float] = None,
+ low: float | None = None,
+ high: float | None = None,
force_stoploss: float = 0,
) -> list[ExitCheckTuple]:
"""
@@ -1390,8 +1389,8 @@ class IStrategy(ABC, HyperStrategyMixin):
current_time: datetime,
current_profit: float,
force_stoploss: float,
- low: Optional[float] = None,
- high: Optional[float] = None,
+ low: float | None = None,
+ high: float | None = None,
after_fill: bool = False,
) -> None:
"""
@@ -1464,8 +1463,8 @@ class IStrategy(ABC, HyperStrategyMixin):
current_time: datetime,
current_profit: float,
force_stoploss: float,
- low: Optional[float] = None,
- high: Optional[float] = None,
+ low: float | None = None,
+ high: float | None = None,
) -> ExitCheckTuple:
"""
Based on current profit of the trade and configured (trailing) stoploss,
@@ -1518,7 +1517,7 @@ class IStrategy(ABC, HyperStrategyMixin):
return ExitCheckTuple(exit_type=ExitType.NONE)
- def min_roi_reached_entry(self, trade_dur: int) -> tuple[Optional[int], Optional[float]]:
+ def min_roi_reached_entry(self, trade_dur: int) -> tuple[int | None, float | None]:
"""
Based on trade duration defines the ROI entry that may have been reached.
:param trade_dur: trade duration in minutes
diff --git a/freqtrade/strategy/parameters.py b/freqtrade/strategy/parameters.py
index 82e930667..282e630d0 100644
--- a/freqtrade/strategy/parameters.py
+++ b/freqtrade/strategy/parameters.py
@@ -7,7 +7,7 @@ import logging
from abc import ABC, abstractmethod
from collections.abc import Sequence
from contextlib import suppress
-from typing import Any, Optional, Union
+from typing import Any, Union
from freqtrade.enums import HyperoptState
from freqtrade.optimize.hyperopt_tools import HyperoptStateContainer
@@ -29,7 +29,7 @@ class BaseParameter(ABC):
Defines a parameter that can be optimized by hyperopt.
"""
- category: Optional[str]
+ category: str | None
default: Any
value: Any
in_space: bool = False
@@ -39,7 +39,7 @@ class BaseParameter(ABC):
self,
*,
default: Any,
- space: Optional[str] = None,
+ space: str | None = None,
optimize: bool = True,
load: bool = True,
**kwargs,
@@ -83,17 +83,17 @@ class BaseParameter(ABC):
class NumericParameter(BaseParameter):
"""Internal parameter used for Numeric purposes"""
- float_or_int = Union[int, float]
+ float_or_int = int | float
default: float_or_int
value: float_or_int
def __init__(
self,
- low: Union[float_or_int, Sequence[float_or_int]],
- high: Optional[float_or_int] = None,
+ low: float_or_int | Sequence[float_or_int],
+ high: float_or_int | None = None,
*,
default: float_or_int,
- space: Optional[str] = None,
+ space: str | None = None,
optimize: bool = True,
load: bool = True,
**kwargs,
@@ -132,11 +132,11 @@ class IntParameter(NumericParameter):
def __init__(
self,
- low: Union[int, Sequence[int]],
- high: Optional[int] = None,
+ low: int | Sequence[int],
+ high: int | None = None,
*,
default: int,
- space: Optional[str] = None,
+ space: str | None = None,
optimize: bool = True,
load: bool = True,
**kwargs,
@@ -186,11 +186,11 @@ class RealParameter(NumericParameter):
def __init__(
self,
- low: Union[float, Sequence[float]],
- high: Optional[float] = None,
+ low: float | Sequence[float],
+ high: float | None = None,
*,
default: float,
- space: Optional[str] = None,
+ space: str | None = None,
optimize: bool = True,
load: bool = True,
**kwargs,
@@ -225,12 +225,12 @@ class DecimalParameter(NumericParameter):
def __init__(
self,
- low: Union[float, Sequence[float]],
- high: Optional[float] = None,
+ low: float | Sequence[float],
+ high: float | None = None,
*,
default: float,
decimals: int = 3,
- space: Optional[str] = None,
+ space: str | None = None,
optimize: bool = True,
load: bool = True,
**kwargs,
@@ -289,8 +289,8 @@ class CategoricalParameter(BaseParameter):
self,
categories: Sequence[Any],
*,
- default: Optional[Any] = None,
- space: Optional[str] = None,
+ default: Any | None = None,
+ space: str | None = None,
optimize: bool = True,
load: bool = True,
**kwargs,
@@ -339,8 +339,8 @@ class BooleanParameter(CategoricalParameter):
def __init__(
self,
*,
- default: Optional[Any] = None,
- space: Optional[str] = None,
+ default: Any | None = None,
+ space: str | None = None,
optimize: bool = True,
load: bool = True,
**kwargs,
diff --git a/freqtrade/strategy/strategy_helper.py b/freqtrade/strategy/strategy_helper.py
index 1a91629d9..71dcc3cbf 100644
--- a/freqtrade/strategy/strategy_helper.py
+++ b/freqtrade/strategy/strategy_helper.py
@@ -1,5 +1,3 @@
-from typing import Optional
-
import pandas as pd
from freqtrade.exchange import timeframe_to_minutes
@@ -13,7 +11,7 @@ def merge_informative_pair(
ffill: bool = True,
append_timeframe: bool = True,
date_column: str = "date",
- suffix: Optional[str] = None,
+ suffix: str | None = None,
) -> pd.DataFrame:
"""
Correctly merge informative samples to the original dataframe, avoiding lookahead bias.
diff --git a/freqtrade/strategy/strategy_wrapper.py b/freqtrade/strategy/strategy_wrapper.py
index a6f74f1c0..9250f54cd 100644
--- a/freqtrade/strategy/strategy_wrapper.py
+++ b/freqtrade/strategy/strategy_wrapper.py
@@ -1,7 +1,8 @@
import logging
+from collections.abc import Callable
from copy import deepcopy
from functools import wraps
-from typing import Any, Callable, TypeVar, cast
+from typing import Any, TypeVar, cast
from freqtrade.exceptions import StrategyError
diff --git a/freqtrade/templates/strategy_analysis_example.ipynb b/freqtrade/templates/strategy_analysis_example.ipynb
index e81ff72ca..2510b38b9 100644
--- a/freqtrade/templates/strategy_analysis_example.ipynb
+++ b/freqtrade/templates/strategy_analysis_example.ipynb
@@ -305,7 +305,7 @@
"metadata": {},
"source": [
"## Analyze the loaded trades for trade parallelism\n",
- "This can be useful to find the best `max_open_trades` parameter, when used with backtesting in conjunction with `--disable-max-market-positions`.\n",
+ "This can be useful to find the best `max_open_trades` parameter, when used with backtesting in conjunction with a very high `max_open_trades` setting.\n",
"\n",
"`analyze_trade_parallelism()` returns a timeseries dataframe with an \"open_trades\" column, specifying the number of open trades for each candle."
]
diff --git a/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2 b/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2
index 07b72610a..84d7f40c8 100644
--- a/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2
+++ b/freqtrade/templates/strategy_subtemplates/strategy_methods_advanced.j2
@@ -13,9 +13,16 @@ def bot_loop_start(self, current_time: datetime, **kwargs) -> None:
"""
pass
-def custom_entry_price(self, pair: str, trade: Optional[Trade],
- current_time: datetime, proposed_rate: float,
- entry_tag: Optional[str], side: str, **kwargs) -> float:
+def custom_entry_price(
+ self,
+ pair: str,
+ trade: Trade | None,
+ current_time: datetime,
+ proposed_rate: float,
+ entry_tag: str | None,
+ side: str,
+ **kwargs,
+) -> float:
"""
Custom entry price logic, returning the new entry price.
@@ -33,9 +40,18 @@ def custom_entry_price(self, pair: str, trade: Optional[Trade],
"""
return proposed_rate
-def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,
- current_time: datetime, proposed_rate: float, current_order_rate: float,
- entry_tag: Optional[str], side: str, **kwargs) -> float:
+def adjust_entry_price(
+ self,
+ trade: Trade,
+ order: Order | None,
+ pair: str,
+ current_time: datetime,
+ proposed_rate: float,
+ current_order_rate: float,
+ entry_tag: str | None,
+ side: str,
+ **kwargs,
+) -> float:
"""
Entry price re-adjustment logic, returning the user desired limit price.
This only executes when a order was already placed, still open (unfilled fully or partially)
@@ -61,9 +77,16 @@ def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,
"""
return current_order_rate
-def custom_exit_price(self, pair: str, trade: Trade,
- current_time: datetime, proposed_rate: float,
- current_profit: float, exit_tag: Optional[str], **kwargs) -> float:
+def custom_exit_price(
+ self,
+ pair: str,
+ trade: Trade,
+ current_time: datetime,
+ proposed_rate: float,
+ current_profit: float,
+ exit_tag: str | None,
+ **kwargs,
+) -> float:
"""
Custom exit price logic, returning the new exit price.
@@ -82,10 +105,19 @@ def custom_exit_price(self, pair: str, trade: Trade,
"""
return proposed_rate
-def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
- proposed_stake: float, min_stake: Optional[float], max_stake: float,
- leverage: float, entry_tag: Optional[str], side: str,
- **kwargs) -> float:
+def custom_stake_amount(
+ self,
+ pair: str,
+ current_time: datetime,
+ current_rate: float,
+ proposed_stake: float,
+ min_stake: float | None,
+ max_stake: float,
+ leverage: float,
+ entry_tag: str | None,
+ side: str,
+ **kwargs,
+) -> float:
"""
Customize stake size for each new trade.
@@ -104,8 +136,16 @@ def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: f
use_custom_stoploss = True
-def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
- current_profit: float, after_fill: bool, **kwargs) -> float:
+def custom_stoploss(
+ self,
+ pair: str,
+ trade: Trade,
+ current_time: datetime,
+ current_rate: float,
+ current_profit: float,
+ after_fill: bool,
+ **kwargs,
+) -> float | None:
"""
Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
e.g. returning -0.05 would create a stoploss 5% below current_rate.
@@ -126,8 +166,15 @@ def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, curre
:return float: New stoploss value, relative to the current_rate
"""
-def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
- current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
+def custom_exit(
+ self,
+ pair: str,
+ trade: Trade,
+ current_time: datetime,
+ current_rate: float,
+ current_profit: float,
+ **kwargs,
+) -> str | bool | None:
"""
Custom exit signal logic indicating that specified position should be sold. Returning a
string or True from this method is equal to setting sell signal on a candle at specified
@@ -150,9 +197,18 @@ def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_r
"""
return None
-def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
- time_in_force: str, current_time: datetime, entry_tag: Optional[str],
- side: str, **kwargs) -> bool:
+def confirm_trade_entry(
+ self,
+ pair: str,
+ order_type: str,
+ amount: float,
+ rate: float,
+ time_in_force: str,
+ current_time: datetime,
+ entry_tag: str | None,
+ side: str,
+ **kwargs,
+) -> bool:
"""
Called right before placing a entry order.
Timing for this function is critical, so avoid doing heavy computations or
@@ -177,9 +233,18 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f
"""
return True
-def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
- rate: float, time_in_force: str, exit_reason: str,
- current_time: datetime, **kwargs) -> bool:
+def confirm_trade_exit(
+ self,
+ pair: str,
+ trade: Trade,
+ order_type: str,
+ amount: float,
+ rate: float,
+ time_in_force: str,
+ exit_reason: str,
+ current_time: datetime,
+ **kwargs,
+) -> bool:
"""
Called right before placing a regular exit order.
Timing for this function is critical, so avoid doing heavy computations or
@@ -206,8 +271,9 @@ def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: f
"""
return True
-def check_entry_timeout(self, pair: str, trade: Trade, order: Order,
- current_time: datetime, **kwargs) -> bool:
+def check_entry_timeout(
+ self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs
+) -> bool:
"""
Check entry timeout function callback.
This method can be used to override the entry-timeout.
@@ -228,8 +294,9 @@ def check_entry_timeout(self, pair: str, trade: Trade, order: Order,
"""
return False
-def check_exit_timeout(self, pair: str, trade: Trade, order: Order,
- current_time: datetime, **kwargs) -> bool:
+def check_exit_timeout(
+ self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs
+) -> bool:
"""
Check exit timeout function callback.
This method can be used to override the exit-timeout.
@@ -250,12 +317,20 @@ def check_exit_timeout(self, pair: str, trade: Trade, order: Order,
"""
return False
-def adjust_trade_position(self, trade: Trade, current_time: datetime,
- current_rate: float, current_profit: float,
- min_stake: Optional[float], max_stake: float,
- current_entry_rate: float, current_exit_rate: float,
- current_entry_profit: float, current_exit_profit: float,
- **kwargs) -> Optional[float]:
+def adjust_trade_position(
+ self,
+ trade: Trade,
+ current_time: datetime,
+ current_rate: float,
+ current_profit: float,
+ min_stake: float | None,
+ max_stake: float,
+ current_entry_rate: float,
+ current_exit_rate: float,
+ current_entry_profit: float,
+ current_exit_profit: float,
+ **kwargs,
+) -> float | None | tuple[float | None, str | None]:
"""
Custom trade adjustment logic, returning the stake amount that a trade should be
increased or decreased.
@@ -284,9 +359,17 @@ def adjust_trade_position(self, trade: Trade, current_time: datetime,
"""
return None
-def leverage(self, pair: str, current_time: datetime, current_rate: float,
- proposed_leverage: float, max_leverage: float, entry_tag: Optional[str],
- side: str, **kwargs) -> float:
+def leverage(
+ self,
+ pair: str,
+ current_time: datetime,
+ current_rate: float,
+ proposed_leverage: float,
+ max_leverage: float,
+ entry_tag: str | None,
+ side: str,
+ **kwargs,
+) -> float:
"""
Customize leverage for each new trade. This method is only called in futures mode.
@@ -302,8 +385,9 @@ def leverage(self, pair: str, current_time: datetime, current_rate: float,
return 1.0
-def order_filled(self, pair: str, trade: Trade, order: Order,
- current_time: datetime, **kwargs) -> None:
+def order_filled(
+ self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs
+) -> None:
"""
Called right after an order fills.
Will be called for all order types (entry, exit, stoploss, position adjustment).
diff --git a/freqtrade/util/datetime_helpers.py b/freqtrade/util/datetime_helpers.py
index 3ab20efb7..255779849 100644
--- a/freqtrade/util/datetime_helpers.py
+++ b/freqtrade/util/datetime_helpers.py
@@ -1,7 +1,6 @@
import re
from datetime import datetime, timezone
from time import time
-from typing import Optional, Union
import humanize
@@ -26,7 +25,7 @@ def dt_utc(
return datetime(year, month, day, hour, minute, second, microsecond, tzinfo=timezone.utc)
-def dt_ts(dt: Optional[datetime] = None) -> int:
+def dt_ts(dt: datetime | None = None) -> int:
"""
Return dt in ms as a timestamp in UTC.
If dt is None, return the current datetime in UTC.
@@ -36,7 +35,7 @@ def dt_ts(dt: Optional[datetime] = None) -> int:
return int(time() * 1000)
-def dt_ts_def(dt: Optional[datetime], default: int = 0) -> int:
+def dt_ts_def(dt: datetime | None, default: int = 0) -> int:
"""
Return dt in ms as a timestamp in UTC.
If dt is None, return the given default.
@@ -46,7 +45,7 @@ def dt_ts_def(dt: Optional[datetime], default: int = 0) -> int:
return default
-def dt_ts_none(dt: Optional[datetime]) -> Optional[int]:
+def dt_ts_none(dt: datetime | None) -> int | None:
"""
Return dt in ms as a timestamp in UTC.
If dt is None, return the given default.
@@ -91,7 +90,7 @@ def dt_humanize_delta(dt: datetime):
return humanize.naturaltime(dt)
-def format_date(date: Optional[datetime]) -> str:
+def format_date(date: datetime | None) -> str:
"""
Return a formatted date string.
Returns an empty string if date is None.
@@ -102,7 +101,7 @@ def format_date(date: Optional[datetime]) -> str:
return ""
-def format_ms_time(date: Union[int, float]) -> str:
+def format_ms_time(date: int | float) -> str:
"""
convert MS date to readable format.
: epoch-string in ms
diff --git a/freqtrade/util/measure_time.py b/freqtrade/util/measure_time.py
index 9ce8dba16..baa94f062 100644
--- a/freqtrade/util/measure_time.py
+++ b/freqtrade/util/measure_time.py
@@ -1,6 +1,6 @@
import logging
import time
-from typing import Callable
+from collections.abc import Callable
from cachetools import TTLCache
diff --git a/freqtrade/util/migrations/__init__.py b/freqtrade/util/migrations/__init__.py
index d8c7dfad3..4e81b1c57 100644
--- a/freqtrade/util/migrations/__init__.py
+++ b/freqtrade/util/migrations/__init__.py
@@ -1,11 +1,9 @@
-from typing import Optional
-
from freqtrade.exchange import Exchange
from freqtrade.util.migrations.binance_mig import migrate_binance_futures_data
from freqtrade.util.migrations.funding_rate_mig import migrate_funding_fee_timeframe
-def migrate_data(config, exchange: Optional[Exchange] = None):
+def migrate_data(config, exchange: Exchange | None = None):
migrate_binance_futures_data(config)
migrate_funding_fee_timeframe(config, exchange)
diff --git a/freqtrade/util/migrations/funding_rate_mig.py b/freqtrade/util/migrations/funding_rate_mig.py
index e53ec339e..060a5a1b6 100644
--- a/freqtrade/util/migrations/funding_rate_mig.py
+++ b/freqtrade/util/migrations/funding_rate_mig.py
@@ -1,5 +1,4 @@
import logging
-from typing import Optional
from freqtrade.constants import Config
from freqtrade.enums import TradingMode
@@ -9,7 +8,7 @@ from freqtrade.exchange import Exchange
logger = logging.getLogger(__name__)
-def migrate_funding_fee_timeframe(config: Config, exchange: Optional[Exchange]):
+def migrate_funding_fee_timeframe(config: Config, exchange: Exchange | None):
from freqtrade.data.history import get_datahandler
if config.get("trading_mode", TradingMode.SPOT) != TradingMode.FUTURES:
diff --git a/freqtrade/util/rich_progress.py b/freqtrade/util/rich_progress.py
index ef06e7f46..6ada4f13f 100644
--- a/freqtrade/util/rich_progress.py
+++ b/freqtrade/util/rich_progress.py
@@ -1,4 +1,4 @@
-from typing import Callable, Optional, Union
+from collections.abc import Callable
from rich.console import ConsoleRenderable, Group, RichCast
from rich.progress import Progress
@@ -8,8 +8,8 @@ class CustomProgress(Progress):
def __init__(
self,
*args,
- cust_objs: Optional[list[ConsoleRenderable]] = None,
- cust_callables: Optional[list[Callable[[], ConsoleRenderable]]] = None,
+ cust_objs: list[ConsoleRenderable] | None = None,
+ cust_callables: list[Callable[[], ConsoleRenderable]] | None = None,
**kwargs,
) -> None:
self._cust_objs = cust_objs or []
@@ -17,7 +17,7 @@ class CustomProgress(Progress):
super().__init__(*args, **kwargs)
- def get_renderable(self) -> Union[ConsoleRenderable, RichCast, str]:
+ def get_renderable(self) -> ConsoleRenderable | RichCast | str:
objs = [obj for obj in self._cust_objs]
for cust_call in self._cust_callables:
objs.append(cust_call())
diff --git a/freqtrade/util/rich_tables.py b/freqtrade/util/rich_tables.py
index 54f941c58..ba232ed75 100644
--- a/freqtrade/util/rich_tables.py
+++ b/freqtrade/util/rich_tables.py
@@ -1,6 +1,6 @@
import sys
from collections.abc import Sequence
-from typing import Any, Optional, Union
+from typing import Any, TypeAlias
from pandas import DataFrame
from rich.console import Console
@@ -8,16 +8,16 @@ from rich.table import Column, Table
from rich.text import Text
-TextOrString = Union[str, Text]
+TextOrString: TypeAlias = str | Text
def print_rich_table(
- tabular_data: Sequence[Union[dict[str, Any], Sequence[TextOrString]]],
+ tabular_data: Sequence[dict[str, Any] | Sequence[TextOrString]],
headers: Sequence[str],
- summary: Optional[str] = None,
+ summary: str | None = None,
*,
justify="right",
- table_kwargs: Optional[dict[str, Any]] = None,
+ table_kwargs: dict[str, Any] | None = None,
) -> None:
table = Table(
*[c if isinstance(c, Column) else Column(c, justify=justify) for c in headers],
@@ -35,7 +35,7 @@ def print_rich_table(
)
else:
- row_to_add: list[Union[str, Text]] = [r if isinstance(r, Text) else str(r) for r in row]
+ row_to_add: list[str | Text] = [r if isinstance(r, Text) else str(r) for r in row]
table.add_row(*row_to_add)
width = None
@@ -55,11 +55,11 @@ def _format_value(value: Any, *, floatfmt: str) -> str:
def print_df_rich_table(
tabular_data: DataFrame,
headers: Sequence[str],
- summary: Optional[str] = None,
+ summary: str | None = None,
*,
show_index=False,
- index_name: Optional[str] = None,
- table_kwargs: Optional[dict[str, Any]] = None,
+ index_name: str | None = None,
+ table_kwargs: dict[str, Any] | None = None,
) -> None:
table = Table(title=summary, **(table_kwargs or {}))
diff --git a/freqtrade/util/template_renderer.py b/freqtrade/util/template_renderer.py
index b2c96fdc5..c9dcfce52 100644
--- a/freqtrade/util/template_renderer.py
+++ b/freqtrade/util/template_renderer.py
@@ -2,8 +2,6 @@
Jinja2 rendering utils, used to generate new strategy and configurations.
"""
-from typing import Optional
-
def render_template(templatefile: str, arguments: dict) -> str:
from jinja2 import Environment, PackageLoader, select_autoescape
@@ -17,7 +15,7 @@ def render_template(templatefile: str, arguments: dict) -> str:
def render_template_with_fallback(
- templatefile: str, templatefallbackfile: str, arguments: Optional[dict] = None
+ templatefile: str, templatefallbackfile: str, arguments: dict | None = None
) -> str:
"""
Use templatefile if possible, otherwise fall back to templatefallbackfile
diff --git a/freqtrade/vendor/qtpylib/indicators.py b/freqtrade/vendor/qtpylib/indicators.py
index 9c92b2f8e..5b56b2aa6 100644
--- a/freqtrade/vendor/qtpylib/indicators.py
+++ b/freqtrade/vendor/qtpylib/indicators.py
@@ -227,7 +227,7 @@ def crossed(series1, series2, direction=None):
if isinstance(series1, np.ndarray):
series1 = pd.Series(series1)
- if isinstance(series2, (float, int, np.ndarray, np.integer, np.floating)):
+ if isinstance(series2, float | int | np.ndarray | np.integer | np.floating):
series2 = pd.Series(index=series1.index, data=series2)
if direction is None or direction == "above":
diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py
index e688b307a..e0265a25e 100644
--- a/freqtrade/wallets.py
+++ b/freqtrade/wallets.py
@@ -4,7 +4,7 @@
import logging
from copy import deepcopy
from datetime import datetime, timedelta
-from typing import NamedTuple, Optional
+from typing import NamedTuple
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT, Config, IntOrInf
from freqtrade.enums import RunMode, TradingMode
@@ -29,7 +29,7 @@ class Wallet(NamedTuple):
class PositionWallet(NamedTuple):
symbol: str
position: float = 0
- leverage: Optional[float] = 0 # Don't use this - it's not guaranteed to be set
+ leverage: float | None = 0 # Don't use this - it's not guaranteed to be set
collateral: float = 0
side: str = "long"
@@ -42,7 +42,7 @@ class Wallets:
self._wallets: dict[str, Wallet] = {}
self._positions: dict[str, PositionWallet] = {}
self.start_cap = config["dry_run_wallet"]
- self._last_wallet_refresh: Optional[datetime] = None
+ self._last_wallet_refresh: datetime | None = None
self.update()
def get_free(self, currency: str) -> float:
@@ -352,10 +352,10 @@ class Wallets:
def validate_stake_amount(
self,
pair: str,
- stake_amount: Optional[float],
- min_stake_amount: Optional[float],
+ stake_amount: float | None,
+ min_stake_amount: float | None,
max_stake_amount: float,
- trade_amount: Optional[float],
+ trade_amount: float | None,
):
if not stake_amount:
logger.debug(f"Stake amount is {stake_amount}, ignoring possible trade for {pair}.")
diff --git a/freqtrade/worker.py b/freqtrade/worker.py
index d618b3dec..61d5d9a64 100644
--- a/freqtrade/worker.py
+++ b/freqtrade/worker.py
@@ -5,8 +5,9 @@ Main Freqtrade worker class.
import logging
import time
import traceback
+from collections.abc import Callable
from os import getpid
-from typing import Any, Callable, Optional
+from typing import Any
import sdnotify
@@ -27,7 +28,7 @@ class Worker:
Freqtradebot worker class
"""
- def __init__(self, args: dict[str, Any], config: Optional[Config] = None) -> None:
+ def __init__(self, args: dict[str, Any], config: Config | None = None) -> None:
"""
Init all variables and objects the bot needs to work
"""
@@ -79,7 +80,7 @@ class Worker:
if state == State.RELOAD_CONFIG:
self._reconfigure()
- def _worker(self, old_state: Optional[State]) -> State:
+ def _worker(self, old_state: State | None) -> State:
"""
The main routine that runs each throttling iteration and handles the states.
:param old_state: the previous service state from the previous call
@@ -141,7 +142,7 @@ class Worker:
self,
func: Callable[..., Any],
throttle_secs: float,
- timeframe: Optional[str] = None,
+ timeframe: str | None = None,
timeframe_offset: float = 1.0,
*args,
**kwargs,
diff --git a/ft_client/freqtrade_client/__init__.py b/ft_client/freqtrade_client/__init__.py
index 31f0e4c6a..d01708a70 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__ = "2024.10-dev"
+__version__ = "2024.11-dev"
if "dev" in __version__:
from pathlib import Path
diff --git a/ft_client/freqtrade_client/ft_rest_client.py b/ft_client/freqtrade_client/ft_rest_client.py
index 577001836..1763cc1a9 100755
--- a/ft_client/freqtrade_client/ft_rest_client.py
+++ b/ft_client/freqtrade_client/ft_rest_client.py
@@ -7,7 +7,7 @@ so it can be used as a standalone script, and can be installed independently.
import json
import logging
-from typing import Any, Optional, Union
+from typing import Any
from urllib.parse import urlencode, urlparse, urlunparse
import requests
@@ -17,8 +17,8 @@ from requests.exceptions import ConnectionError
logger = logging.getLogger("ft_rest_client")
-ParamsT = Optional[dict[str, Any]]
-PostDataT = Optional[Union[dict[str, Any], list[dict[str, Any]]]]
+ParamsT = dict[str, Any] | None
+PostDataT = dict[str, Any] | list[dict[str, Any]] | None
class FtRestClient:
@@ -35,7 +35,7 @@ class FtRestClient:
if username and password:
self._session.auth = (username, password)
- def _call(self, method, apipath, params: Optional[dict] = None, data=None, files=None):
+ def _call(self, method, apipath, params: dict | None = None, data=None, files=None):
if str(method).upper() not in ("GET", "POST", "PUT", "DELETE"):
raise ValueError(f"invalid method <{method}>")
basepath = f"{self._serverurl}/api/v1/{apipath}"
diff --git a/pyproject.toml b/pyproject.toml
index 1db932cdc..a5472d554 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -246,7 +246,6 @@ reportTypedDictNotRequiredAccess = false # 27
[tool.ruff]
line-length = 100
extend-exclude = [".env", ".venv"]
-target-version = "py39"
[tool.ruff.lint]
extend-select = [
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 1907e9a0e..d413ae2c2 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -7,12 +7,12 @@
-r docs/requirements-docs.txt
coveralls==4.0.1
-ruff==0.7.0
-mypy==1.12.1
+ruff==0.7.3
+mypy==1.13.0
pre-commit==4.0.1
pytest==8.3.3
pytest-asyncio==0.24.0
-pytest-cov==5.0.0
+pytest-cov==6.0.0
pytest-mock==3.14.0
pytest-random-order==1.1.1
pytest-timeout==2.3.1
diff --git a/requirements-freqai-rl.txt b/requirements-freqai-rl.txt
index 14388df86..cfb07f74c 100644
--- a/requirements-freqai-rl.txt
+++ b/requirements-freqai-rl.txt
@@ -3,9 +3,9 @@
# Required for freqai-rl
torch==2.2.2; sys_platform == 'darwin' and platform_machine == 'x86_64'
-torch==2.5.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
+torch==2.5.1; sys_platform != 'darwin' or platform_machine != 'x86_64'
gymnasium==0.29.1
stable_baselines3==2.3.2
sb3_contrib>=2.2.1
# Progress bar for stable-baselines3 and sb3-contrib
-tqdm==4.66.5
+tqdm==4.67.0
diff --git a/requirements.txt b/requirements.txt
index 4f0fc2532..c27ab0201 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,12 +4,12 @@ bottleneck==1.4.2
numexpr==2.10.1
pandas-ta==0.3.14b
-ccxt==4.4.20
+ccxt==4.4.29
cryptography==42.0.8; platform_machine == 'armv7l'
cryptography==43.0.3; platform_machine != 'armv7l'
aiohttp==3.10.10
SQLAlchemy==2.0.36
-python-telegram-bot==21.6
+python-telegram-bot==21.7
# can't be hard-pinned due to telegram-bot pinning httpx with ~
httpx>=0.24.1
humanize==4.11.0
@@ -24,8 +24,8 @@ pycoingecko==3.1.0
jinja2==3.1.4
tables==3.10.1
joblib==1.4.2
-rich==13.9.2
-pyarrow==17.0.0; platform_machine != 'armv7l'
+rich==13.9.4
+pyarrow==18.0.0; platform_machine != 'armv7l'
# find first, C search in arrays
py_find_1st==1.1.6
@@ -33,13 +33,13 @@ py_find_1st==1.1.6
# Load ticker files 30% faster
python-rapidjson==1.20
# Properly format api responses
-orjson==3.10.9
+orjson==3.10.11
# Notify systemd
sdnotify==0.3.2
# API Server
-fastapi==0.115.2
+fastapi==0.115.4
pydantic==2.9.2
uvicorn==0.32.0
pyjwt==2.9.0
@@ -57,8 +57,8 @@ pytz==2024.2
schedule==1.2.2
#WS Messages
-websockets==13.1
-janus==1.0.0
+websockets==14.0
+janus==1.1.0
ast-comments==1.2.2
-packaging==24.1
+packaging==24.2
diff --git a/scripts/ws_client.py b/scripts/ws_client.py
index 81e539e26..46dd2da10 100755
--- a/scripts/ws_client.py
+++ b/scripts/ws_client.py
@@ -253,8 +253,7 @@ async def create_client(
except (
socket.gaierror,
ConnectionRefusedError,
- websockets.exceptions.InvalidStatusCode,
- websockets.exceptions.InvalidMessage,
+ websockets.exceptions.InvalidHandshake,
) as e:
logger.error(f"Connection Refused - {e} retrying in {sleep_time}s")
await asyncio.sleep(sleep_time)
diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py
index 28baffd5a..a6c195f2f 100644
--- a/tests/commands/test_commands.py
+++ b/tests/commands/test_commands.py
@@ -1,5 +1,6 @@
import json
import re
+import shutil
from datetime import datetime, timedelta
from io import BytesIO
from pathlib import Path
@@ -44,7 +45,7 @@ from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.persistence.models import init_db
from freqtrade.persistence.pairlock_middleware import PairLocks
-from freqtrade.util import dt_floor_day, dt_now, dt_utc
+from freqtrade.util import dt_utc
from tests.conftest import (
CURRENT_TEST_STRATEGY,
EXMS,
@@ -571,7 +572,7 @@ def test_create_datadir_failed(caplog):
assert log_has("`create-userdir` requires --userdir to be set.", caplog)
-def test_create_datadir(caplog, mocker):
+def test_create_datadir(mocker):
cud = mocker.patch(
"freqtrade.configuration.directory_operations.create_userdata_dir", MagicMock()
)
@@ -585,26 +586,46 @@ def test_create_datadir(caplog, mocker):
assert csf.call_count == 1
-def test_start_new_strategy(mocker, caplog):
- wt_mock = mocker.patch.object(Path, "write_text", MagicMock())
- mocker.patch.object(Path, "exists", MagicMock(return_value=False))
+def test_start_new_strategy(caplog, user_dir):
+ strategy_dir = user_dir / "strategies"
+ strategy_dir.mkdir(parents=True, exist_ok=True)
+ assert strategy_dir.is_dir()
args = ["new-strategy", "--strategy", "CoolNewStrategy"]
start_new_strategy(get_args(args))
+ assert strategy_dir.exists()
+ assert (strategy_dir / "CoolNewStrategy.py").exists()
- assert wt_mock.call_count == 1
- assert "CoolNewStrategy" in wt_mock.call_args_list[0][0][0]
assert log_has_re("Writing strategy to .*", caplog)
- mocker.patch("freqtrade.configuration.setup_utils_configuration")
- mocker.patch.object(Path, "exists", MagicMock(return_value=True))
with pytest.raises(
OperationalException, match=r".* already exists. Please choose another Strategy Name\."
):
start_new_strategy(get_args(args))
+ args = ["new-strategy", "--strategy", "CoolNewStrategy", "--strategy-path", str(user_dir)]
+ start_new_strategy(get_args(args))
+ assert (user_dir / "CoolNewStrategy.py").exists()
-def test_start_new_strategy_no_arg(mocker, caplog):
+ # strategy-path that doesn't exist
+ args = [
+ "new-strategy",
+ "--strategy",
+ "CoolNewStrategy",
+ "--strategy-path",
+ str(user_dir / "nonexistent"),
+ ]
+ start_new_strategy(get_args(args))
+ assert (user_dir / "CoolNewStrategy.py").exists()
+
+ assert log_has_re("Creating strategy directory .*", caplog)
+ assert (user_dir / "nonexistent").is_dir()
+ assert (user_dir / "nonexistent" / "CoolNewStrategy.py").exists()
+
+ shutil.rmtree(str(user_dir))
+
+
+def test_start_new_strategy_no_arg():
args = [
"new-strategy",
]
@@ -757,7 +778,13 @@ def test_download_data_keyboardInterrupt(mocker, markets):
assert dl_mock.call_count == 1
-def test_download_data_timerange(mocker, markets):
+@pytest.mark.parametrize("time", ["00:00", "00:03", "00:30", "23:56"])
+@pytest.mark.parametrize(
+ "tzoffset",
+ ["00:00", "+01:00", "-01:00", "+05:00", "-05:00"],
+)
+def test_download_data_timerange(mocker, markets, time_machine, time, tzoffset):
+ time_machine.move_to(f"2024-11-01 {time}:00 {tzoffset}")
dl_mock = mocker.patch(
"freqtrade.data.history.history_utils.refresh_backtest_ohlcv_data",
MagicMock(return_value=["ETH/BTC", "XRP/BTC"]),
@@ -797,8 +824,9 @@ def test_download_data_timerange(mocker, markets):
start_download_data(pargs)
assert dl_mock.call_count == 1
# 20days ago
- days_ago = dt_floor_day(dt_now() - timedelta(days=20)).timestamp()
- assert dl_mock.call_args_list[0][1]["timerange"].startts == days_ago
+ days_ago = datetime.now() - timedelta(days=20)
+ days_ago = dt_utc(days_ago.year, days_ago.month, days_ago.day)
+ assert dl_mock.call_args_list[0][1]["timerange"].startts == days_ago.timestamp()
dl_mock.reset_mock()
args = [
diff --git a/tests/conftest.py b/tests/conftest.py
index 99c42de5f..517170860 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -5,7 +5,6 @@ import re
from copy import deepcopy
from datetime import datetime, timedelta, timezone
from pathlib import Path
-from typing import Optional
from unittest.mock import MagicMock, Mock, PropertyMock
import numpy as np
@@ -124,7 +123,7 @@ def get_args(args):
return Arguments(args).get_parsed_arg()
-def generate_trades_history(n_rows, start_date: Optional[datetime] = None, days=5):
+def generate_trades_history(n_rows, start_date: datetime | None = None, days=5):
np.random.seed(42)
if not start_date:
start_date = datetime(2020, 1, 1, tzinfo=timezone.utc)
@@ -206,7 +205,7 @@ def generate_test_data_raw(timeframe: str, size: int, start: str = "2020-07-05",
"""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
- return list(list(x) for x in zip(*(df[x].values.tolist() for x in df.columns)))
+ return list(list(x) for x in zip(*(df[x].values.tolist() for x in df.columns), strict=False))
# Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines
@@ -363,8 +362,8 @@ def patch_get_signal(
exit_long=False,
enter_short=False,
exit_short=False,
- enter_tag: Optional[str] = None,
- exit_tag: Optional[str] = None,
+ enter_tag: str | None = None,
+ exit_tag: str | None = None,
) -> None:
"""
:param mocker: mocker to patch IStrategy class
@@ -395,7 +394,7 @@ def patch_get_signal(
freqtrade.exchange.refresh_latest_ohlcv = lambda p: None
-def create_mock_trades(fee, is_short: Optional[bool] = False, use_db: bool = True):
+def create_mock_trades(fee, is_short: bool | None = False, use_db: bool = True):
"""
Create some fake trades ...
:param is_short: Optional bool, None creates a mix of long and short trades.
@@ -474,7 +473,7 @@ def create_mock_trades_with_leverage(fee, use_db: bool = True):
Trade.session.flush()
-def create_mock_trades_usdt(fee, is_short: Optional[bool] = False, use_db: bool = True):
+def create_mock_trades_usdt(fee, is_short: bool | None = False, use_db: bool = True):
"""
Create some fake trades ...
"""
diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py
index b9dee6d59..9b79983af 100644
--- a/tests/data/test_btanalysis.py
+++ b/tests/data/test_btanalysis.py
@@ -503,7 +503,7 @@ def test_calculate_max_drawdown2():
]
dates = [dt_utc(2020, 1, 1) + timedelta(days=i) for i in range(len(values))]
- df = DataFrame(zip(values, dates), columns=["profit", "open_date"])
+ 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()
@@ -522,11 +522,11 @@ def test_calculate_max_drawdown2():
assert drawdown.drawdown_abs == 0.091755
assert pytest.approx(drawdown.relative_account_drawdown) == 0.32129575
- df = DataFrame(zip(values[:5], dates[:5]), columns=["profit", "open_date"])
+ df = DataFrame(zip(values[:5], dates[:5], strict=False), columns=["profit", "open_date"])
with pytest.raises(ValueError, match="No losing trade, therefore no drawdown."):
calculate_max_drawdown(df, date_col="open_date", value_col="profit")
- df1 = DataFrame(zip(values[:5], dates[:5]), columns=["profit", "open_date"])
+ 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")
@@ -548,7 +548,7 @@ def test_calculate_max_drawdown_abs(profits, relative, highd, lowdays, result, r
"""
init_date = datetime(2020, 1, 1, tzinfo=timezone.utc)
dates = [init_date + timedelta(days=i) for i in range(len(profits))]
- df = DataFrame(zip(profits, dates), columns=["profit_abs", "open_date"])
+ 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()
diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py
index 35d0b18dd..407cdc9bd 100644
--- a/tests/exchange/test_exchange.py
+++ b/tests/exchange/test_exchange.py
@@ -1069,7 +1069,6 @@ def test_create_dry_run_order(default_conf, mocker, side, exchange_name, leverag
assert order["type"] == "limit"
assert order["symbol"] == "ETH/BTC"
assert order["amount"] == 1
- assert order["leverage"] == leverage
assert order["cost"] == 1 * 200
@@ -1274,6 +1273,9 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
exchange._set_leverage = MagicMock()
exchange.set_margin_mode = MagicMock()
+ # Only applies to gate
+ price_req = exchange._ft_has.get("marketOrderRequiresPrice", False)
+
order = exchange.create_order(
pair="XLTCUSDT", ordertype=ordertype, side=side, amount=1, rate=rate, leverage=1.0
)
@@ -1286,7 +1288,9 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
assert api_mock.create_order.call_args[0][1] == ordertype
assert api_mock.create_order.call_args[0][2] == side
assert api_mock.create_order.call_args[0][3] == 1
- assert api_mock.create_order.call_args[0][4] is rate
+ assert api_mock.create_order.call_args[0][4] == (
+ rate if price_req or not (bool(marketprice) and side == "sell") else None
+ )
assert exchange._set_leverage.call_count == 0
assert exchange.set_margin_mode.call_count == 0
@@ -1364,7 +1368,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == "buy"
assert api_mock.create_order.call_args[0][3] == 1
- if exchange._order_needs_price(order_type):
+ if exchange._order_needs_price("buy", order_type):
assert api_mock.create_order.call_args[0][4] == 200
else:
assert api_mock.create_order.call_args[0][4] is None
@@ -1511,7 +1515,7 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == "buy"
assert api_mock.create_order.call_args[0][3] == 1
- if exchange._order_needs_price(order_type):
+ if exchange._order_needs_price("buy", order_type):
assert api_mock.create_order.call_args[0][4] == 200
else:
assert api_mock.create_order.call_args[0][4] is None
@@ -1556,7 +1560,7 @@ def test_sell_prod(default_conf, mocker, exchange_name):
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == "sell"
assert api_mock.create_order.call_args[0][3] == 1
- if exchange._order_needs_price(order_type):
+ if exchange._order_needs_price("sell", order_type):
assert api_mock.create_order.call_args[0][4] == 200
else:
assert api_mock.create_order.call_args[0][4] is None
@@ -1666,7 +1670,7 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == "sell"
assert api_mock.create_order.call_args[0][3] == 1
- if exchange._order_needs_price(order_type):
+ if exchange._order_needs_price("sell", order_type):
assert api_mock.create_order.call_args[0][4] == 200
else:
assert api_mock.create_order.call_args[0][4] is None
diff --git a/tests/freqtradebot/test_freqtradebot.py b/tests/freqtradebot/test_freqtradebot.py
index 836f84580..bd7a0dd47 100644
--- a/tests/freqtradebot/test_freqtradebot.py
+++ b/tests/freqtradebot/test_freqtradebot.py
@@ -3483,16 +3483,17 @@ def test_locked_pairs(
exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS),
)
trade.close(ticker_usdt_sell_down()["bid"])
- assert freqtrade.strategy.is_pair_locked(trade.pair, side="*")
+ assert not freqtrade.strategy.is_pair_locked(trade.pair, side="*")
# Both sides are locked
- assert freqtrade.strategy.is_pair_locked(trade.pair, side="long")
- assert freqtrade.strategy.is_pair_locked(trade.pair, side="short")
+ assert freqtrade.strategy.is_pair_locked(trade.pair, side="long") != is_short
+ assert freqtrade.strategy.is_pair_locked(trade.pair, side="short") == is_short
# reinit - should buy other pair.
caplog.clear()
freqtrade.enter_positions()
+ direction = "short" if is_short else "long"
- assert log_has_re(rf"Pair {trade.pair} \* is locked.*", caplog)
+ assert log_has_re(rf"Pair {trade.pair} {direction} is locked.*", caplog)
@pytest.mark.parametrize("is_short", [False, True])
diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py
index 15e19cd75..dfce6d8c5 100644
--- a/tests/optimize/__init__.py
+++ b/tests/optimize/__init__.py
@@ -1,5 +1,5 @@
from datetime import timedelta
-from typing import NamedTuple, Optional
+from typing import NamedTuple
from pandas import DataFrame
@@ -20,7 +20,7 @@ class BTrade(NamedTuple):
exit_reason: ExitType
open_tick: int
close_tick: int
- enter_tag: Optional[str] = None
+ enter_tag: str | None = None
is_short: bool = False
@@ -36,15 +36,15 @@ class BTContainer(NamedTuple):
profit_perc: float
trailing_stop: bool = False
trailing_only_offset_is_reached: bool = False
- trailing_stop_positive: Optional[float] = None
+ trailing_stop_positive: float | None = None
trailing_stop_positive_offset: float = 0.0
use_exit_signal: bool = False
use_custom_stoploss: bool = False
- custom_entry_price: Optional[float] = None
- custom_exit_price: Optional[float] = None
+ custom_entry_price: float | None = None
+ custom_exit_price: float | None = None
leverage: float = 1.0
- timeout: Optional[int] = None
- adjust_entry_price: Optional[float] = None
+ timeout: int | None = None
+ adjust_entry_price: float | None = None
def _get_frame_time_from_offset(offset):
diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py
index e0a9e4480..63fa1bd96 100644
--- a/tests/optimize/test_backtest_detail.py
+++ b/tests/optimize/test_backtest_detail.py
@@ -1121,6 +1121,70 @@ tc53 = BTContainer(
trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2, is_short=True)],
)
+# Test 54: Switch position from long to short
+tc54 = BTContainer(
+ data=[
+ # D O H L C V EL XL ES Xs BT
+ [0, 5000, 5050, 4950, 5000, 6172, 1, 0, 0, 0],
+ [1, 5000, 5000, 4951, 5000, 6172, 0, 0, 0, 0],
+ [2, 4910, 5150, 4910, 5100, 6172, 0, 0, 1, 0], # Enter short signal being ignored
+ [3, 5100, 5100, 4950, 4950, 6172, 0, 1, 1, 0], # exit - re-enter short
+ [4, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 1],
+ [5, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 0],
+ ],
+ stop_loss=-0.10,
+ roi={"0": 0.10},
+ profit_perc=0.00,
+ use_exit_signal=True,
+ trades=[
+ BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4, is_short=False),
+ BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=4, close_tick=5, is_short=True),
+ ],
+)
+
+# Test 55: Switch position from short to long
+tc55 = BTContainer(
+ data=[
+ # D O H L C V EL XL ES Xs BT
+ [0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0],
+ [1, 5000, 5000, 4951, 5000, 6172, 1, 0, 0, 0], # Enter long signal being ignored
+ [2, 4910, 5150, 4910, 5100, 6172, 1, 0, 0, 1], # Exit - reenter long
+ [3, 5100, 5100, 4950, 4950, 6172, 0, 0, 0, 0],
+ [4, 5000, 5100, 4950, 4950, 6172, 0, 1, 0, 0],
+ [5, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 0],
+ ],
+ stop_loss=-0.10,
+ roi={"0": 0.10},
+ profit_perc=-0.04,
+ use_exit_signal=True,
+ trades=[
+ BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=3, is_short=True),
+ BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=3, close_tick=5, is_short=False),
+ ],
+)
+
+# Test 56: Switch position from long to short
+tc56 = BTContainer(
+ data=[
+ # D O H L C V EL XL ES Xs BT
+ [0, 5000, 5050, 4950, 5000, 6172, 1, 0, 0, 0],
+ [1, 5000, 5000, 4951, 5000, 6172, 0, 0, 0, 0],
+ [2, 4910, 5150, 4910, 5100, 6172, 0, 0, 1, 0], # exit on stoploss - re-enter short
+ [3, 5100, 5100, 4888, 4950, 6172, 0, 0, 0, 0],
+ [4, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 1],
+ [5, 5000, 5100, 4950, 4950, 6172, 0, 0, 0, 0],
+ ],
+ stop_loss=-0.02,
+ roi={"0": 0.10},
+ profit_perc=-0.0,
+ use_exit_signal=True,
+ trades=[
+ BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=3, is_short=False),
+ BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=3, close_tick=5, is_short=True),
+ ],
+)
+
+
TESTS = [
tc0,
tc1,
@@ -1176,6 +1240,9 @@ TESTS = [
tc51,
tc52,
tc53,
+ tc54,
+ tc55,
+ tc56,
]
diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py
index f064247c6..7c14956e7 100644
--- a/tests/optimize/test_backtesting.py
+++ b/tests/optimize/test_backtesting.py
@@ -189,7 +189,6 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
"--timeframe",
"1m",
"--enable-position-stacking",
- "--disable-max-market-positions",
"--timerange",
":100",
"--export-filename",
@@ -214,10 +213,6 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
assert "position_stacking" in config
assert log_has("Parameter --enable-position-stacking detected ...", caplog)
- assert "use_max_market_positions" in config
- assert log_has("Parameter --disable-max-market-positions detected ...", caplog)
- assert log_has("max_open_trades set to unlimited ...", caplog)
-
assert "timerange" in config
assert log_has("Parameter --timerange detected: {} ...".format(config["timerange"]), caplog)
@@ -1681,6 +1676,131 @@ def test_backtest_multi_pair_detail(
assert len(evaluate_result_multi(results["results"], "5m", 1)) == 0
+@pytest.mark.parametrize("use_detail", [True, False])
+@pytest.mark.parametrize("pair", ["ADA/USDT", "LTC/USDT"])
+@pytest.mark.parametrize("tres", [0, 20, 30])
+def test_backtest_multi_pair_detail_simplified(
+ default_conf_usdt,
+ fee,
+ mocker,
+ tres,
+ pair,
+ use_detail,
+):
+ """
+ literally the same as test_backtest_multi_pair_detail
+ but with an "always enter" strategy, exiting after about half of the candle duration.
+ """
+
+ def _always_buy(dataframe, metadata):
+ """
+ Buy every xth candle - sell every other xth -2 (hold on to pairs a bit)
+ """
+ dataframe["enter_long"] = 1
+ dataframe["enter_short"] = 0
+ dataframe["exit_short"] = 0
+ return dataframe
+
+ def custom_exit(
+ trade: Trade,
+ current_time: datetime,
+ **kwargs,
+ ) -> str | bool | None:
+ # Exit within the same candle.
+ if (trade.open_date_utc + timedelta(minutes=20)) < current_time:
+ return "exit after 20 minutes"
+
+ default_conf_usdt.update(
+ {
+ "runmode": "backtest",
+ "stoploss": -1.0,
+ "minimal_roi": {"0": 100},
+ }
+ )
+
+ if use_detail:
+ default_conf_usdt["timeframe_detail"] = "5m"
+
+ 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_fee", fee)
+ patch_exchange(mocker)
+
+ raw_candles_5m = generate_test_data("5m", 1000, "2022-01-03 12:00:00+00:00")
+ raw_candles = ohlcv_fill_up_missing_data(raw_candles_5m, "1h", "dummy")
+
+ pairs = ["ADA/USDT", "DASH/USDT", "ETH/USDT", "LTC/USDT", "NXT/USDT"]
+ data = {pair: raw_candles for pair in pairs}
+ detail_data = {pair: raw_candles_5m for pair in pairs}
+
+ # Only use 500 lines to increase performance
+ data = trim_dictlist(data, -200)
+
+ # Remove data for one pair from the beginning of the data
+ if tres > 0:
+ data[pair] = data[pair][tres:].reset_index()
+ default_conf_usdt["timeframe"] = "1h"
+ default_conf_usdt["max_open_trades"] = 3
+
+ backtesting = Backtesting(default_conf_usdt)
+ vr_spy = mocker.spy(backtesting, "validate_row")
+ bl_spy = mocker.spy(backtesting, "backtest_loop")
+ backtesting.detail_data = detail_data
+ backtesting._set_strategy(backtesting.strategylist[0])
+ backtesting.strategy.bot_loop_start = MagicMock()
+ backtesting.strategy.advise_entry = _always_buy # Override
+ backtesting.strategy.advise_exit = _always_buy # Override
+ backtesting.strategy.custom_exit = custom_exit # Override
+
+ processed = backtesting.strategy.advise_all_indicators(data)
+ min_date, max_date = get_timerange(processed)
+
+ backtest_conf = {
+ "processed": deepcopy(processed),
+ "start_date": min_date,
+ "end_date": max_date,
+ }
+
+ results = backtesting.backtest(**backtest_conf)
+
+ # bot_loop_start is called once per candle.
+ # assert backtesting.strategy.bot_loop_start.call_count == 83
+ # Validated row once per candle and pair
+ assert vr_spy.call_count == 415
+
+ if use_detail:
+ # Backtest loop is called once per candle per pair
+ # Exact numbers depend on trade state - but should be around 3_800
+ assert bl_spy.call_count > 3_350
+ assert bl_spy.call_count < 3_800
+ else:
+ assert bl_spy.call_count < 995
+
+ # Make sure we have parallel trades
+ assert len(evaluate_result_multi(results["results"], "1h", 2)) > 0
+ # make sure we don't have trades with more than configured max_open_trades
+ assert len(evaluate_result_multi(results["results"], "1h", 3)) == 0
+
+ # # Cached data correctly removed amounts
+ offset = 1 if tres == 0 else 0
+ removed_candles = len(data[pair]) - offset
+ assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, "1h")[0]) == removed_candles
+ assert (
+ len(backtesting.dataprovider.get_analyzed_dataframe("NXT/USDT", "1h")[0])
+ == len(data["NXT/USDT"]) - 1
+ )
+
+ backtesting.strategy.max_open_trades = 1
+ backtesting.config.update({"max_open_trades": 1})
+ backtest_conf = {
+ "processed": deepcopy(processed),
+ "start_date": min_date,
+ "end_date": max_date,
+ }
+ results = backtesting.backtest(**backtest_conf)
+ assert len(evaluate_result_multi(results["results"], "1h", 1)) == 0
+
+
@pytest.mark.parametrize("use_detail", [True, False])
def test_backtest_multi_pair_long_short_switch(
default_conf_usdt,
@@ -1774,7 +1894,7 @@ def test_backtest_multi_pair_long_short_switch(
if use_detail:
# Backtest loop is called once per candle per pair
- assert bl_spy.call_count == 1071
+ assert bl_spy.call_count == 1523
else:
assert bl_spy.call_count == 479
@@ -1784,7 +1904,7 @@ def test_backtest_multi_pair_long_short_switch(
assert len(evaluate_result_multi(results["results"], "5m", 1)) == 0
# Expect 26 results initially
- assert len(results["results"]) == 30
+ assert len(results["results"]) == 53
def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
@@ -1811,14 +1931,12 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
"--timerange",
"1510694220-1510700340",
"--enable-position-stacking",
- "--disable-max-market-positions",
]
args = get_args(args)
start_backtesting(args)
# check the logs, that will contain the backtest result
exists = [
"Parameter -i/--timeframe detected ... Using timeframe: 1m ...",
- "Ignoring max_open_trades (--disable-max-market-positions was used) ...",
"Parameter --timerange detected: 1510694220-1510700340 ...",
f"Using data directory: {testdatadir} ...",
"Loading data from 2017-11-14 20:57:00 up to 2017-11-14 22:59:00 (0 days).",
@@ -1892,7 +2010,6 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
"--timerange",
"1510694220-1510700340",
"--enable-position-stacking",
- "--disable-max-market-positions",
"--strategy-list",
CURRENT_TEST_STRATEGY,
"StrategyTestV2",
@@ -1909,7 +2026,6 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
# check the logs, that will contain the backtest result
exists = [
"Parameter -i/--timeframe detected ... Using timeframe: 1m ...",
- "Ignoring max_open_trades (--disable-max-market-positions was used) ...",
"Parameter --timerange detected: 1510694220-1510700340 ...",
f"Using data directory: {testdatadir} ...",
"Loading data from 2017-11-14 20:57:00 up to 2017-11-14 22:59:00 (0 days).",
@@ -2030,7 +2146,6 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
"--timerange",
"1510694220-1510700340",
"--enable-position-stacking",
- "--disable-max-market-positions",
"--breakdown",
"day",
"--strategy-list",
@@ -2043,7 +2158,6 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
# check the logs, that will contain the backtest result
exists = [
"Parameter -i/--timeframe detected ... Using timeframe: 1m ...",
- "Ignoring max_open_trades (--disable-max-market-positions was used) ...",
"Parameter --timerange detected: 1510694220-1510700340 ...",
f"Using data directory: {testdatadir} ...",
"Loading data from 2017-11-14 20:57:00 up to 2017-11-14 22:59:00 (0 days).",
@@ -2468,7 +2582,6 @@ def test_backtest_start_multi_strat_caching(
"--timerange",
"1510694220-1510700340",
"--enable-position-stacking",
- "--disable-max-market-positions",
"--cache",
cache,
"--strategy-list",
@@ -2495,7 +2608,6 @@ def test_backtest_start_multi_strat_caching(
exists = [
"Running backtesting for Strategy StrategyTestV2",
"Running backtesting for Strategy StrategyTestV3",
- "Ignoring max_open_trades (--disable-max-market-positions was used) ...",
"Backtesting with data from 2017-11-14 21:17:00 up to 2017-11-14 22:59:00 (0 days).",
]
elif run_id == "2" and min_backtest_date < start_time:
@@ -2508,7 +2620,6 @@ def test_backtest_start_multi_strat_caching(
exists = [
"Reusing result of previous backtest for StrategyTestV2",
"Running backtesting for Strategy StrategyTestV3",
- "Ignoring max_open_trades (--disable-max-market-positions was used) ...",
"Backtesting with data from 2017-11-14 21:17:00 up to 2017-11-14 22:59:00 (0 days).",
]
assert backtestmock.call_count == 1
diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py
index a9f697629..e7b03da6f 100644
--- a/tests/optimize/test_hyperopt.py
+++ b/tests/optimize/test_hyperopt.py
@@ -102,7 +102,6 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
"--timerange",
":100",
"--enable-position-stacking",
- "--disable-max-market-positions",
"--epochs",
"1000",
"--spaces",
@@ -126,10 +125,6 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
assert "position_stacking" in config
assert log_has("Parameter --enable-position-stacking detected ...", caplog)
- assert "use_max_market_positions" in config
- assert log_has("Parameter --disable-max-market-positions detected ...", caplog)
- assert log_has("max_open_trades set to unlimited ...", caplog)
-
assert "timerange" in config
assert log_has("Parameter --timerange detected: {} ...".format(config["timerange"]), caplog)
diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py
index 05160c74a..a5e237a45 100644
--- a/tests/persistence/test_persistence.py
+++ b/tests/persistence/test_persistence.py
@@ -2144,6 +2144,7 @@ def test_Trade_object_idem():
"bt_trades_open",
"bt_trades_open_pp",
"bt_open_open_trade_count",
+ "bt_open_open_trade_count_candle",
"bt_total_profit",
"from_json",
)
@@ -2682,6 +2683,36 @@ def test_select_filled_orders(fee):
assert len(orders) == 0
+@pytest.mark.usefixtures("init_persistence")
+def test_select_filled_orders_usdt(fee):
+ create_mock_trades_usdt(fee)
+
+ trades = Trade.get_trades().all()
+
+ # Closed buy order, no sell order
+ orders = trades[0].select_filled_orders("buy")
+ assert isinstance(orders, list)
+ assert len(orders) == 1
+ assert orders[0].amount == 2.0
+ assert orders[0].filled == 2.0
+ assert orders[0].side == "buy"
+ assert orders[0].price == 10.0
+ assert orders[0].stake_amount == 20
+ assert orders[0].stake_amount_filled == 20
+
+ orders = trades[3].select_filled_orders("buy")
+ assert isinstance(orders, list)
+ assert len(orders) == 0
+ orders = trades[3].select_filled_or_open_orders()
+ assert isinstance(orders, list)
+ assert len(orders) == 1
+ assert orders[0].price == 2.0
+ assert orders[0].amount == 10
+ assert orders[0].filled == 0
+ assert orders[0].stake_amount == 20
+ assert orders[0].stake_amount_filled == 0
+
+
@pytest.mark.usefixtures("init_persistence")
def test_order_to_ccxt(limit_buy_order_open, limit_sell_order_usdt_open):
order = Order.parse_from_ccxt_object(limit_buy_order_open, "mocked", "buy")
diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py
index 1c138cc55..a4f66a702 100644
--- a/tests/plugins/test_pairlist.py
+++ b/tests/plugins/test_pairlist.py
@@ -2450,7 +2450,7 @@ def test_MarketCapPairList_filter_special_no_pair_from_coingecko(
assert pm.whitelist == []
-def test_MarketCapPairList_exceptions(mocker, default_conf_usdt):
+def test_MarketCapPairList_exceptions(mocker, default_conf_usdt, caplog):
exchange = get_patched_exchange(mocker, default_conf_usdt)
default_conf_usdt["pairlists"] = [{"method": "MarketCapPairList"}]
with pytest.raises(OperationalException, match=r"`number_assets` not specified.*"):
@@ -2458,13 +2458,11 @@ def test_MarketCapPairList_exceptions(mocker, default_conf_usdt):
PairListManager(exchange, default_conf_usdt)
default_conf_usdt["pairlists"] = [
- {"method": "MarketCapPairList", "number_assets": 20, "max_rank": 260}
+ {"method": "MarketCapPairList", "number_assets": 20, "max_rank": 500}
]
- with pytest.raises(
- OperationalException, match="This filter only support marketcap rank up to 250."
- ):
+ with caplog.at_level(logging.WARNING):
PairListManager(exchange, default_conf_usdt)
-
+ assert log_has_re("The max rank you have set \\(500\\) is quite high", caplog)
# Test invalid coinmarkets list
mocker.patch(
"freqtrade.plugins.pairlist.MarketCapPairList.FtCoinGeckoApi.get_coins_categories_list",
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index 971e13846..2d1f34fcd 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -2885,3 +2885,85 @@ async def test_telegram_list_custom_data(default_conf_usdt, update, ticker, fee,
) in msg_mock.call_args_list[2][0][0]
msg_mock.reset_mock()
+
+
+def test_noficiation_settings(default_conf_usdt, mocker):
+ (telegram, _, _) = get_telegram_testobject(mocker, default_conf_usdt)
+ telegram._config["telegram"].update(
+ {
+ "notification_settings": {
+ "status": "silent",
+ "warning": "on",
+ "startup": "off",
+ "entry": "silent",
+ "entry_fill": "on",
+ "entry_cancel": "silent",
+ "exit": {
+ "roi": "silent",
+ "emergency_exit": "on",
+ "force_exit": "on",
+ "exit_signal": "silent",
+ "trailing_stop_loss": "on",
+ "stop_loss": "on",
+ "stoploss_on_exchange": "on",
+ "custom_exit": "silent",
+ "partial_exit": "off",
+ },
+ "exit_fill": {
+ "roi": "silent",
+ "partial_exit": "off",
+ "*": "silent", # Default to silent
+ },
+ "exit_cancel": "on",
+ "protection_trigger": "off",
+ "protection_trigger_global": "on",
+ "strategy_msg": "off",
+ "show_candle": "off",
+ }
+ }
+ )
+
+ loudness = telegram._message_loudness
+
+ assert loudness({"type": RPCMessageType.ENTRY, "exit_reason": ""}) == "silent"
+ assert loudness({"type": RPCMessageType.ENTRY_FILL, "exit_reason": ""}) == "on"
+ assert loudness({"type": RPCMessageType.EXIT, "exit_reason": ""}) == "on"
+ # Default to silent due to "*" definition
+ assert loudness({"type": RPCMessageType.EXIT_FILL, "exit_reason": ""}) == "silent"
+ assert loudness({"type": RPCMessageType.PROTECTION_TRIGGER, "exit_reason": ""}) == "off"
+ assert loudness({"type": RPCMessageType.EXIT, "exit_reason": "roi"}) == "silent"
+ assert loudness({"type": RPCMessageType.EXIT, "exit_reason": "partial_exit"}) == "off"
+ # Not given key defaults to on
+ assert loudness({"type": RPCMessageType.EXIT, "exit_reason": "cust_exit112"}) == "on"
+
+ assert loudness({"type": RPCMessageType.EXIT_FILL, "exit_reason": "roi"}) == "silent"
+ assert loudness({"type": RPCMessageType.EXIT_FILL, "exit_reason": "partial_exit"}) == "off"
+ # Default to silent due to "*" definition
+ assert loudness({"type": RPCMessageType.EXIT_FILL, "exit_reason": "cust_exit112"}) == "silent"
+
+ # Simplified setup for exit
+ telegram._config["telegram"].update(
+ {
+ "notification_settings": {
+ "status": "silent",
+ "warning": "on",
+ "startup": "off",
+ "entry": "silent",
+ "entry_fill": "on",
+ "entry_cancel": "silent",
+ "exit": "off",
+ "exit_cancel": "on",
+ "exit_fill": "on",
+ "protection_trigger": "off",
+ "protection_trigger_global": "on",
+ "strategy_msg": "off",
+ "show_candle": "off",
+ }
+ }
+ )
+
+ assert loudness({"type": RPCMessageType.EXIT_FILL, "exit_reason": "roi"}) == "on"
+ # All regular exits are off
+ assert loudness({"type": RPCMessageType.EXIT, "exit_reason": "roi"}) == "off"
+ assert loudness({"type": RPCMessageType.EXIT, "exit_reason": "partial_exit"}) == "off"
+ assert loudness({"type": RPCMessageType.EXIT, "exit_reason": "cust_exit112"}) == "off"
diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py
index 007c7655e..e87810111 100644
--- a/tests/strategy/strats/strategy_test_v3.py
+++ b/tests/strategy/strats/strategy_test_v3.py
@@ -1,7 +1,6 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from datetime import datetime
-from typing import Optional
import talib.abstract as ta
from pandas import DataFrame
@@ -175,7 +174,7 @@ class StrategyTestV3(IStrategy):
current_rate: float,
proposed_leverage: float,
max_leverage: float,
- entry_tag: Optional[str],
+ entry_tag: str | None,
side: str,
**kwargs,
) -> float:
@@ -190,14 +189,14 @@ class StrategyTestV3(IStrategy):
current_time: datetime,
current_rate: float,
current_profit: float,
- min_stake: Optional[float],
+ min_stake: float | None,
max_stake: float,
current_entry_rate: float,
current_exit_rate: float,
current_entry_profit: float,
current_exit_profit: float,
**kwargs,
- ) -> Optional[float]:
+ ) -> float | None:
if current_profit < -0.0075:
orders = trade.select_filled_orders(trade.entry_side)
return round(orders[0].stake_amount, 0)
diff --git a/tests/strategy/strats/strategy_test_v3_custom_entry_price.py b/tests/strategy/strats/strategy_test_v3_custom_entry_price.py
index 956766d14..2689db27a 100644
--- a/tests/strategy/strats/strategy_test_v3_custom_entry_price.py
+++ b/tests/strategy/strats/strategy_test_v3_custom_entry_price.py
@@ -1,7 +1,6 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from datetime import datetime
-from typing import Optional
from pandas import DataFrame
from strategy_test_v3 import StrategyTestV3
@@ -34,10 +33,10 @@ class StrategyTestV3CustomEntryPrice(StrategyTestV3):
def custom_entry_price(
self,
pair: str,
- trade: Optional[Trade],
+ trade: Trade | None,
current_time: datetime,
proposed_rate: float,
- entry_tag: Optional[str],
+ entry_tag: str | None,
side: str,
**kwargs,
) -> float:
diff --git a/tests/test_configuration.py b/tests/test_configuration.py
index 829bf699a..8011ded96 100644
--- a/tests/test_configuration.py
+++ b/tests/test_configuration.py
@@ -489,7 +489,6 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog, tmp_pa
"--timeframe",
"1m",
"--enable-position-stacking",
- "--disable-max-market-positions",
"--timerange",
":100",
"--export",
@@ -518,10 +517,6 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog, tmp_pa
assert "position_stacking" in config
assert log_has("Parameter --enable-position-stacking detected ...", caplog)
- assert "use_max_market_positions" in config
- assert log_has("Parameter --disable-max-market-positions detected ...", caplog)
- assert log_has("max_open_trades set to unlimited ...", caplog)
-
assert "timerange" in config
assert log_has("Parameter --timerange detected: {} ...".format(config["timerange"]), caplog)
@@ -570,8 +565,6 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non
assert "position_stacking" not in config
- assert "use_max_market_positions" not in config
-
assert "timerange" not in config
assert "export" in config